Add safe UI widget area drawing API

Make Screen generic over its display kind: Screen<Tty> and
Screen<Virtual>. The latter is for "cached" renderings we want to keep
and copy to the actual screen when the Component::draw() method is
called. Only Screen<Tty> can write to stdout and it needs an stdout
handle.

Add a generation integer field to Screen, that changes each time it is
resized. This way, we can track if "stale" areas are used and panic on
runtime (in debug mode).

Introduce a new type, Area, that keeps metadata about a subsection of a
Screen, and the generation it came from. New areas can only be created
from a Screen and by operating on an Area to create subsections of it.

This way, it's impossible to make an area refer to (x, y) cells outside
the screen generation of its provenance. If stabilised this API should
eliminate all out of bounds accesses in CellBuffers.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/312/head
Manos Pitsidianakis 2023-10-23 13:56:13 +03:00
parent 7645ff1b87
commit 0e3a0c4b70
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
35 changed files with 4700 additions and 4264 deletions

View File

@ -47,9 +47,9 @@ impl<const MIN: u8, const MAX: u8> ArgCheck<MIN, MAX> {
} }
); );
}; };
let is_empty = dbg!(input.trim().is_empty()); let is_empty = input.trim().is_empty();
if is_empty && dbg!(MIN) > 0 { if is_empty && MIN > 0 {
return dbg!(Err(CommandError::WrongNumberOfArguments { return Err(CommandError::WrongNumberOfArguments {
too_many: false, too_many: false,
takes: (MIN, MAX.into()), takes: (MIN, MAX.into()),
given: 0, given: 0,
@ -60,7 +60,7 @@ impl<const MIN: u8, const MAX: u8> ArgCheck<MIN, MAX> {
MIN MIN
) )
.into(), .into(),
})); });
} }
*self = Self::BeforeArgument { *self = Self::BeforeArgument {
so_far: 0, so_far: 0,

View File

@ -147,8 +147,8 @@ impl Component for ContactManager {
self.initialized = true; self.initialized = true;
} }
let upper_left = upper_left!(area); let upper_left = area.upper_left();
let bottom_right = bottom_right!(area); let bottom_right = area.bottom_right();
if self.dirty { if self.dirty {
let (width, _height) = self.content.size(); let (width, _height) = self.content.size();
@ -157,7 +157,7 @@ impl Component for ContactManager {
(upper_left, set_y(bottom_right, get_y(upper_left) + 1)), (upper_left, set_y(bottom_right, get_y(upper_left) + 1)),
self.theme_default, self.theme_default,
); );
grid.copy_area_with_break(&self.content, area, ((0, 0), (width - 1, 0))); grid.copy_area(&self.content, area, ((0, 0), (width - 1, 0)));
self.dirty = false; self.dirty = false;
} }

File diff suppressed because it is too large Load Diff

View File

@ -21,4 +21,4 @@
pub mod list; pub mod list;
pub mod editor; //pub mod editor;

View File

@ -329,7 +329,7 @@ impl JobManager {
if idx >= self.length { if idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = nth_row_area(area, idx % rows); let new_area = area.nth_row(idx % rows);
self.data_columns self.data_columns
.draw(grid, idx, self.cursor_pos, grid.bounds_iter(new_area)); .draw(grid, idx, self.cursor_pos, grid.bounds_iter(new_area));
let row_attr = if highlight { let row_attr = if highlight {
@ -351,18 +351,14 @@ impl JobManager {
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
_ = self _ = self
.data_columns .data_columns
.recalc_widths((width!(area), height!(area)), top_idx); .recalc_widths((area.width(), area.height()), top_idx);
grid.clear_area(area, self.theme_default); grid.clear_area(area, self.theme_default);
/* copy table columns */ /* copy table columns */
self.data_columns self.data_columns
.draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area)); .draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area));
/* highlight cursor */ /* highlight cursor */
grid.change_theme(area.nth_row(self.cursor_pos % rows), self.highlight_theme);
grid.change_theme(
nth_row_area(area, self.cursor_pos % rows),
self.highlight_theme,
);
/* clear gap if available height is more than count of entries */ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length { if top_idx + rows > self.length {
@ -386,45 +382,43 @@ impl Component for JobManager {
if !self.initialized { if !self.initialized {
self.initialize(context); self.initialize(context);
} }
{ let area = area.nth_row(0);
// Draw column headers. // Draw column headers.
let area = nth_row_area(area, 0); grid.clear_area(area, self.theme_default);
grid.clear_area(area, self.theme_default); let mut x_offset = 0;
let mut x_offset = 0; let (upper_left, bottom_right) = area;
let (upper_left, bottom_right) = area; for (i, (h, w)) in Self::HEADERS.iter().zip(self.min_width).enumerate() {
for (i, (h, w)) in Self::HEADERS.iter().zip(self.min_width).enumerate() { grid.write_string(
h,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD,
(pos_inc(upper_left, (x_offset, 0)), bottom_right),
None,
);
if self.sort_col as usize == i {
use SortOrder::*;
let arrow = match (grid.ascii_drawing, self.sort_order) {
(true, Asc) => DataColumns::<5>::ARROW_UP_ASCII,
(true, Desc) => DataColumns::<5>::ARROW_DOWN_ASCII,
(false, Asc) => DataColumns::<5>::ARROW_UP,
(false, Desc) => DataColumns::<5>::ARROW_DOWN,
};
grid.write_string( grid.write_string(
h, arrow,
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD, self.theme_default.attrs,
(pos_inc(upper_left, (x_offset, 0)), bottom_right), (pos_inc(upper_left, (x_offset + h.len(), 0)), bottom_right),
None, None,
); );
if self.sort_col as usize == i {
use SortOrder::*;
let arrow = match (grid.ascii_drawing, self.sort_order) {
(true, Asc) => DataColumns::<5>::ARROW_UP_ASCII,
(true, Desc) => DataColumns::<5>::ARROW_DOWN_ASCII,
(false, Asc) => DataColumns::<5>::ARROW_UP,
(false, Desc) => DataColumns::<5>::ARROW_DOWN,
};
grid.write_string(
arrow,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
(pos_inc(upper_left, (x_offset + h.len(), 0)), bottom_right),
None,
);
}
x_offset += w + 2;
} }
context.dirty_areas.push_back(area); x_offset += w + 2;
} }
context.dirty_areas.push_back(area);
// Draw entry rows. // Draw entry rows.
if let Some(area) = skip_rows(area, 1) { if let Some(area) = area.skip_rows(1) {
self.draw_list(grid, area, context); self.draw_list(grid, area, context);
} }
self.dirty = false; self.dirty = false;

View File

@ -85,11 +85,11 @@ pub use crate::mail::*;
pub mod notifications; pub mod notifications;
pub mod mailbox_management; //pub mod mailbox_management;
pub use mailbox_management::*; //pub use mailbox_management::*;
pub mod jobs_view; //pub mod jobs_view;
pub use jobs_view::*; //pub use jobs_view::*;
#[cfg(feature = "svgscreenshot")] #[cfg(feature = "svgscreenshot")]
pub mod svg; pub mod svg;

View File

@ -42,8 +42,8 @@ use crate::{accounts::JobRequest, jobs::JoinHandle, terminal::embed::EmbedTermin
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
pub mod gpg; pub mod gpg;
pub mod edit_attachments; //pub mod edit_attachments;
use edit_attachments::*; //use edit_attachments::*;
pub mod hooks; pub mod hooks;
@ -87,6 +87,11 @@ impl std::ops::DerefMut for EmbedStatus {
} }
} }
#[derive(Debug)]
struct Embedded {
status: EmbedStatus,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Composer { pub struct Composer {
reply_context: Option<(MailboxHash, EnvelopeHash)>, reply_context: Option<(MailboxHash, EnvelopeHash)>,
@ -100,8 +105,8 @@ pub struct Composer {
mode: ViewMode, mode: ViewMode,
embed_area: Area, embedded: Option<Embedded>,
embed: Option<EmbedStatus>, embed_dimensions: (usize, usize),
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
gpg_state: gpg::GpgComposeState, gpg_state: gpg::GpgComposeState,
dirty: bool, dirty: bool,
@ -114,9 +119,9 @@ pub struct Composer {
#[derive(Debug)] #[derive(Debug)]
enum ViewMode { enum ViewMode {
Discard(ComponentId, UIDialog<char>), Discard(ComponentId, UIDialog<char>),
EditAttachments { //EditAttachments {
widget: EditAttachments, // widget: EditAttachments,
}, //},
Edit, Edit,
Embed, Embed,
SelectRecipients(UIDialog<Address>), SelectRecipients(UIDialog<Address>),
@ -131,9 +136,9 @@ impl ViewMode {
matches!(self, ViewMode::Edit) matches!(self, ViewMode::Edit)
} }
fn is_edit_attachments(&self) -> bool { //fn is_edit_attachments(&self) -> bool {
matches!(self, ViewMode::EditAttachments { .. }) // matches!(self, ViewMode::EditAttachments { .. })
} //}
} }
impl std::fmt::Display for Composer { impl std::fmt::Display for Composer {
@ -180,8 +185,8 @@ impl Composer {
gpg_state: gpg::GpgComposeState::default(), gpg_state: gpg::GpgComposeState::default(),
dirty: true, dirty: true,
has_changes: false, has_changes: false,
embed_area: ((0, 0), (0, 0)), embedded: None,
embed: None, embed_dimensions: (80, 20),
initialized: false, initialized: false,
id: ComponentId::default(), id: ComponentId::default(),
} }
@ -707,7 +712,7 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)), area.skip_rows(1),
None, None,
); );
} else { } else {
@ -720,7 +725,7 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)), area.skip_rows(1),
None, None,
); );
} }
@ -755,7 +760,7 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), area.skip_rows(2),
None, None,
); );
} else { } else {
@ -768,7 +773,7 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), area.skip_rows(2),
None, None,
); );
} }
@ -782,7 +787,7 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 3)), bottom_right!(area)), area.skip_rows(3),
None, None,
); );
} else { } else {
@ -795,40 +800,33 @@ To: {}
theme_default.bg theme_default.bg
}, },
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 3)), bottom_right!(area)), area.skip_rows(3),
None, None,
); );
for (i, a) in self.draft.attachments().iter().enumerate() { for (i, a) in self.draft.attachments().iter().enumerate() {
if let Some(name) = a.content_type().name() { grid.write_string(
grid.write_string( &if let Some(name) = a.content_type().name() {
&format!( format!(
"[{}] \"{}\", {} {}", "[{}] \"{}\", {} {}",
i, i,
name, name,
a.content_type(), a.content_type(),
melib::BytesDisplay(a.raw.len()) melib::BytesDisplay(a.raw.len())
), )
theme_default.fg, } else {
theme_default.bg, format!(
theme_default.attrs,
(pos_inc(upper_left!(area), (0, 4 + i)), bottom_right!(area)),
None,
);
} else {
grid.write_string(
&format!(
"[{}] {} {}", "[{}] {} {}",
i, i,
a.content_type(), a.content_type(),
melib::BytesDisplay(a.raw.len()) melib::BytesDisplay(a.raw.len())
), )
theme_default.fg, },
theme_default.bg, theme_default.fg,
theme_default.attrs, theme_default.bg,
(pos_inc(upper_left!(area), (0, 4 + i)), bottom_right!(area)), theme_default.attrs,
None, area.skip_rows(4 + i),
); None,
} );
} }
} }
} }
@ -859,12 +857,7 @@ To: {}
impl Component for Composer { impl Component for Composer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area); if area.height() < 4 {
let bottom_right = bottom_right!(area);
let upper_left = set_y(upper_left, get_y(upper_left) + 1);
if height!(area) < 4 {
return; return;
} }
@ -917,34 +910,25 @@ impl Component for Composer {
}; };
*/ */
let header_area = ( let header_area = area
set_x(upper_left, mid + 1), .take_rows(header_height)
( .skip_cols(mid + 1)
get_x(bottom_right).saturating_sub(mid), .skip_cols_from_end(mid);
get_y(upper_left) + header_height,
),
);
let attachments_no = self.draft.attachments().len(); let attachments_no = self.draft.attachments().len();
let attachment_area = ( let attachment_area = area
( .skip_rows(header_height)
mid + 1, .skip_rows(
get_y(bottom_right).saturating_sub(4 + attachments_no), area.height()
), .saturating_sub(header_area.height() + 4 + attachments_no),
pos_dec(bottom_right, (mid, 0)), )
); .skip_cols(mid + 1);
let body_area = ( let body_area = area
( .skip_rows(header_height)
get_x(upper_left!(header_area)), .skip_rows_from_end(attachment_area.height());
get_y(bottom_right!(header_area)) + 1,
),
(
get_x(bottom_right!(header_area)),
get_y(upper_left!(attachment_area)).saturating_sub(1),
),
);
let (x, y) = grid.write_string( grid.clear_area(area.nth_row(0), crate::conf::value(context, "highlight"));
grid.write_string(
if self.reply_context.is_some() { if self.reply_context.is_some() {
"COMPOSING REPLY" "COMPOSING REPLY"
} else { } else {
@ -953,18 +937,15 @@ impl Component for Composer {
crate::conf::value(context, "highlight").fg, crate::conf::value(context, "highlight").fg,
crate::conf::value(context, "highlight").bg, crate::conf::value(context, "highlight").bg,
crate::conf::value(context, "highlight").attrs, crate::conf::value(context, "highlight").attrs,
( area.nth_row(0),
pos_dec(upper_left!(header_area), (0, 1)),
bottom_right!(header_area),
),
None, None,
); );
grid.clear_area(((x, y), (set_y(bottom_right, y))), theme_default);
/*
grid.change_theme( grid.change_theme(
( (
set_x(pos_dec(upper_left!(header_area), (0, 1)), x), set_x(pos_dec(header_area.upper_left(), (0, 1)), x),
set_y(bottom_right!(header_area), y), set_y(header_area.bottom_right(), y),
), ),
crate::conf::value(context, "highlight"), crate::conf::value(context, "highlight"),
); );
@ -987,22 +968,20 @@ impl Component for Composer {
), ),
theme_default, theme_default,
); );
*/
/* Regardless of view mode, do the following */ /* Regardless of view mode, do the following */
self.form.draw(grid, header_area, context); self.form.draw(grid, header_area, context);
if let Some(ref mut embed_pty) = self.embed { if let Some(ref mut embedded) = self.embedded {
let embed_area = (upper_left!(header_area), bottom_right!(body_area)); let embed_pty = &mut embedded.status;
let embed_area = area;
match embed_pty { match embed_pty {
EmbedStatus::Running(_, _) => { EmbedStatus::Running(_, _) => {
let mut guard = embed_pty.lock().unwrap(); let mut guard = embed_pty.lock().unwrap();
grid.clear_area(embed_area, theme_default); grid.clear_area(embed_area, theme_default);
grid.copy_area( grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.area());
guard.grid.buffer(), guard.set_terminal_size((embed_area.width(), embed_area.height()));
embed_area,
((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
);
guard.set_terminal_size((width!(embed_area), height!(embed_area)));
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
self.dirty = false; self.dirty = false;
return; return;
@ -1010,11 +989,7 @@ impl Component for Composer {
EmbedStatus::Stopped(_, _) => { EmbedStatus::Stopped(_, _) => {
let guard = embed_pty.lock().unwrap(); let guard = embed_pty.lock().unwrap();
grid.copy_area( grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.buffer().area());
guard.grid.buffer(),
embed_area,
((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
);
grid.change_colors(embed_area, Color::Byte(8), theme_default.bg); grid.change_colors(embed_area, Color::Byte(8), theme_default.bg);
let our_map: ShortcutMap = let our_map: ShortcutMap =
account_settings!(context[self.account_hash].shortcuts.composing) account_settings!(context[self.account_hash].shortcuts.composing)
@ -1033,19 +1008,7 @@ impl Component for Composer {
stopped_message.len(), stopped_message.len(),
std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()), std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()),
); );
let inner_area = create_box( let inner_area = create_box(grid, area.center_inside((max_len + 5, 5)));
grid,
(
pos_inc(upper_left!(body_area), (1, 0)),
pos_inc(
upper_left!(body_area),
(
std::cmp::min(max_len + 5, width!(body_area)),
std::cmp::min(5, height!(body_area)),
),
),
),
);
grid.clear_area(inner_area, theme_default); grid.clear_area(inner_area, theme_default);
for (i, l) in [ for (i, l) in [
stopped_message.as_str(), stopped_message.as_str(),
@ -1060,11 +1023,8 @@ impl Component for Composer {
theme_default.fg, theme_default.fg,
theme_default.bg, theme_default.bg,
theme_default.attrs, theme_default.attrs,
( inner_area.skip_rows(i),
pos_inc((0, i), upper_left!(inner_area)), None, //Some(get_x(inner_area.upper_left())),
bottom_right!(inner_area),
),
Some(get_x(upper_left!(inner_area))),
); );
} }
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -1073,10 +1033,10 @@ impl Component for Composer {
} }
} }
} else { } else {
self.embed_area = (upper_left!(header_area), bottom_right!(body_area)); self.embed_dimensions = (area.width(), area.height());
} }
if self.pager.size().0 > width!(body_area) { if self.pager.size().0 > body_area.width() {
self.pager.set_initialised(false); self.pager.set_initialised(false);
} }
// Force clean pager area, because if body height is less than body_area it will // Force clean pager area, because if body height is less than body_area it will
@ -1087,23 +1047,26 @@ impl Component for Composer {
match self.cursor { match self.cursor {
Cursor::Headers => { Cursor::Headers => {
/*
grid.change_theme( grid.change_theme(
( (
pos_dec(upper_left!(body_area), (1, 0)), pos_dec(body_area.upper_left(), (1, 0)),
pos_dec( pos_dec(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), set_y(body_area.upper_left(), get_y(body_area.bottom_right())),
(1, 0), (1, 0),
), ),
), ),
theme_default, theme_default,
); );
*/
} }
Cursor::Body => { Cursor::Body => {
/*
grid.change_theme( grid.change_theme(
( (
pos_dec(upper_left!(body_area), (1, 0)), pos_dec(body_area.upper_left(), (1, 0)),
pos_dec( pos_dec(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), set_y(body_area.upper_left(), get_y(body_area.bottom_right())),
(1, 0), (1, 0),
), ),
), ),
@ -1117,32 +1080,26 @@ impl Component for Composer {
}, },
}, },
); );
*/
} }
Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {} Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {}
} }
//if !self.mode.is_edit_attachments() {
self.draw_attachments(grid, attachment_area, context);
//}
match self.mode { match self.mode {
ViewMode::Edit | ViewMode::Embed => {} ViewMode::Edit | ViewMode::Embed => {}
ViewMode::EditAttachments { ref mut widget } => { //ViewMode::EditAttachments { ref mut widget } => {
let inner_area = create_box( // let inner_area = create_box(grid, area);
grid, // (EditAttachmentsRefMut {
(upper_left!(body_area), bottom_right!(attachment_area)), // inner: widget,
); // draft: &mut self.draft,
(EditAttachmentsRefMut { // })
inner: widget, // .draw(grid, inner_area, context);
draft: &mut self.draft, //}
})
.draw(
grid,
(
pos_inc(upper_left!(inner_area), (1, 1)),
bottom_right!(inner_area),
),
context,
);
}
ViewMode::Send(ref mut s) => { ViewMode::Send(ref mut s) => {
s.draw(grid, area, context); s.draw(grid, body_area, context);
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
ViewMode::SelectEncryptKey( ViewMode::SelectEncryptKey(
@ -1152,25 +1109,22 @@ impl Component for Composer {
keys: _, keys: _,
}, },
) => { ) => {
widget.draw(grid, area, context); widget.draw(grid, body_area, context);
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
ViewMode::SelectEncryptKey(_, _) => {} ViewMode::SelectEncryptKey(_, _) => {}
ViewMode::SelectRecipients(ref mut s) => { ViewMode::SelectRecipients(ref mut s) => {
s.draw(grid, area, context); s.draw(grid, body_area, context);
} }
ViewMode::Discard(_, ref mut s) => { ViewMode::Discard(_, ref mut s) => {
/* Let user choose whether to quit with/without saving or cancel */ /* Let user choose whether to quit with/without saving or cancel */
s.draw(grid, area, context); s.draw(grid, body_area, context);
} }
ViewMode::WaitingForSendResult(ref mut s, _) => { ViewMode::WaitingForSendResult(ref mut s, _) => {
/* Let user choose whether to wait for success or cancel */ /* Let user choose whether to wait for success or cancel */
s.draw(grid, area, context); s.draw(grid, body_area, context);
} }
} }
if !self.mode.is_edit_attachments() {
self.draw_attachments(grid, attachment_area, context);
}
self.dirty = false; self.dirty = false;
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
@ -1239,23 +1193,22 @@ impl Component for Composer {
return true; return true;
} }
} }
(ViewMode::EditAttachments { ref mut widget }, _) => { //(ViewMode::EditAttachments { ref mut widget }, _) => {
if (EditAttachmentsRefMut { // if (EditAttachmentsRefMut {
inner: widget, // inner: widget,
draft: &mut self.draft, // draft: &mut self.draft,
}) // })
.process_event(event, context) // .process_event(event, context)
{ // {
if matches!( // if matches!(
widget.buttons.result(), // widget.buttons.result(),
Some(FormButtonActions::Cancel | FormButtonActions::Accept) // Some(FormButtonActions::Cancel | FormButtonActions::Accept)
) { // ) { self.mode = ViewMode::Edit;
self.mode = ViewMode::Edit; // }
} // self.set_dirty(true);
self.set_dirty(true); // return true;
return true; // }
} //}
}
(ViewMode::Send(ref selector), UIEvent::FinishedUIDialog(id, result)) (ViewMode::Send(ref selector), UIEvent::FinishedUIDialog(id, result))
if selector.id() == *id => if selector.id() == *id =>
{ {
@ -1598,10 +1551,23 @@ impl Component for Composer {
return true; return true;
} }
UIEvent::EmbedInput((Key::Ctrl('z'), _)) => { UIEvent::EmbedInput((Key::Ctrl('z'), _)) => {
self.embed.as_ref().unwrap().lock().unwrap().stop(); self.embedded
match self.embed.take() { .as_ref()
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => { .unwrap()
self.embed = Some(EmbedStatus::Stopped(e, f)); .status
.lock()
.unwrap()
.stop();
match self.embedded.take() {
Some(Embedded {
status: EmbedStatus::Running(e, f),
})
| Some(Embedded {
status: EmbedStatus::Stopped(e, f),
}) => {
self.embedded = Some(Embedded {
status: EmbedStatus::Stopped(e, f),
});
} }
_ => {} _ => {}
} }
@ -1611,13 +1577,13 @@ impl Component for Composer {
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::EmbedInput((ref k, ref b)) => { UIEvent::EmbedInput((ref k, ref b)) => {
if let Some(ref mut embed) = self.embed { if let Some(ref mut embed) = self.embedded {
let mut embed_guard = embed.lock().unwrap(); let mut embed_guard = embed.status.lock().unwrap();
if embed_guard.write_all(b).is_err() { if embed_guard.write_all(b).is_err() {
match embed_guard.is_active() { match embed_guard.is_active() {
Ok(WaitStatus::Exited(_, exit_code)) => { Ok(WaitStatus::Exited(_, exit_code)) => {
drop(embed_guard); drop(embed_guard);
let embed = self.embed.take(); let embedded = self.embedded.take();
if exit_code != 0 { if exit_code != 0 {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
None, None,
@ -1629,7 +1595,10 @@ impl Component for Composer {
melib::error::ErrorKind::External, melib::error::ErrorKind::External,
)), )),
)); ));
} else if let Some(EmbedStatus::Running(_, file)) = embed { } else if let Some(Embedded {
status: EmbedStatus::Running(_, file),
}) = embedded
{
self.update_from_file(file, context); self.update_from_file(file, context);
} }
self.initialized = false; self.initialized = false;
@ -1643,10 +1612,16 @@ impl Component for Composer {
Ok(WaitStatus::PtraceEvent(_, _, _)) Ok(WaitStatus::PtraceEvent(_, _, _))
| Ok(WaitStatus::PtraceSyscall(_)) => { | Ok(WaitStatus::PtraceSyscall(_)) => {
drop(embed_guard); drop(embed_guard);
match self.embed.take() { match self.embedded.take() {
Some(EmbedStatus::Running(e, f)) Some(Embedded {
| Some(EmbedStatus::Stopped(e, f)) => { status: EmbedStatus::Running(e, f),
self.embed = Some(EmbedStatus::Stopped(e, f)); })
| Some(Embedded {
status: EmbedStatus::Stopped(e, f),
}) => {
self.embedded = Some(Embedded {
status: EmbedStatus::Stopped(e, f),
});
} }
_ => {} _ => {}
} }
@ -1659,10 +1634,16 @@ impl Component for Composer {
} }
Ok(WaitStatus::Stopped(_, _)) => { Ok(WaitStatus::Stopped(_, _)) => {
drop(embed_guard); drop(embed_guard);
match self.embed.take() { match self.embedded.take() {
Some(EmbedStatus::Running(e, f)) Some(Embedded {
| Some(EmbedStatus::Stopped(e, f)) => { status: EmbedStatus::Running(e, f),
self.embed = Some(EmbedStatus::Stopped(e, f)); })
| Some(Embedded {
status: EmbedStatus::Stopped(e, f),
}) => {
self.embedded = Some(Embedded {
status: EmbedStatus::Stopped(e, f),
});
} }
_ => {} _ => {}
} }
@ -1691,7 +1672,7 @@ impl Component for Composer {
)), )),
)); ));
self.initialized = false; self.initialized = false;
self.embed = None; self.embedded = None;
self.mode = ViewMode::Edit; self.mode = ViewMode::Edit;
context context
.replies .replies
@ -1707,7 +1688,7 @@ impl Component for Composer {
)); ));
drop(embed_guard); drop(embed_guard);
self.initialized = false; self.initialized = false;
self.embed = None; self.embedded = None;
self.mode = ViewMode::Edit; self.mode = ViewMode::Edit;
context context
.replies .replies
@ -1804,21 +1785,34 @@ impl Component for Composer {
&& self.cursor == Cursor::Attachments && self.cursor == Cursor::Attachments
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) => && shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{ {
self.mode = ViewMode::EditAttachments { //self.mode = ViewMode::EditAttachments {
widget: EditAttachments::new(Some(self.account_hash)), // widget: EditAttachments::new(Some(self.account_hash)),
}; //};
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if self.embed.is_some() if self.embedded.is_some()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) => && shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{ {
self.embed.as_ref().unwrap().lock().unwrap().wake_up(); self.embedded
match self.embed.take() { .as_ref()
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => { .unwrap()
self.embed = Some(EmbedStatus::Running(e, f)); .status
.lock()
.unwrap()
.wake_up();
match self.embedded.take() {
Some(Embedded {
status: EmbedStatus::Running(e, f),
})
| Some(Embedded {
status: EmbedStatus::Stopped(e, f),
}) => {
self.embedded = Some(Embedded {
status: EmbedStatus::Running(e, f),
});
} }
_ => {} _ => {}
} }
@ -1830,11 +1824,16 @@ impl Component for Composer {
return true; return true;
} }
UIEvent::Input(Key::Ctrl('c')) UIEvent::Input(Key::Ctrl('c'))
if self.embed.is_some() && self.embed.as_ref().unwrap().is_stopped() => if self.embedded.is_some()
&& self.embedded.as_ref().unwrap().status.is_stopped() =>
{ {
match self.embed.take() { match self.embedded.take() {
Some(EmbedStatus::Running(embed, file)) Some(Embedded {
| Some(EmbedStatus::Stopped(embed, file)) => { status: EmbedStatus::Running(embed, file),
})
| Some(Embedded {
status: EmbedStatus::Stopped(embed, file),
}) => {
let guard = embed.lock().unwrap(); let guard = embed.lock().unwrap();
guard.wake_up(); guard.wake_up();
guard.terminate(); guard.terminate();
@ -1908,18 +1907,26 @@ impl Component for Composer {
if *account_settings!(context[self.account_hash].composing.embed) { if *account_settings!(context[self.account_hash].composing.embed) {
match crate::terminal::embed::create_pty( match crate::terminal::embed::create_pty(
width!(self.embed_area), self.embed_dimensions.0,
height!(self.embed_area), self.embed_dimensions.1,
[editor, f.path().display().to_string()].join(" "), [editor, f.path().display().to_string()].join(" "),
) { ) {
Ok(embed) => { Ok(embed) => {
self.embed = Some(EmbedStatus::Running(embed, f)); self.embedded = Some(Embedded {
status: EmbedStatus::Running(embed, f),
});
self.set_dirty(true); self.set_dirty(true);
context context
.replies .replies
.push_back(UIEvent::ChangeMode(UIMode::Embed)); .push_back(UIEvent::ChangeMode(UIMode::Embed));
context.replies.push_back(UIEvent::Fork(ForkType::Embed( context.replies.push_back(UIEvent::Fork(ForkType::Embed(
self.embed.as_ref().unwrap().lock().unwrap().child_pid, self.embedded
.as_ref()
.unwrap()
.status
.lock()
.unwrap()
.child_pid,
))); )));
self.mode = ViewMode::Embed; self.mode = ViewMode::Embed;
} }
@ -2199,13 +2206,13 @@ impl Component for Composer {
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
match self.mode { match self.mode {
ViewMode::Embed => true, ViewMode::Embed => true,
ViewMode::EditAttachments { ref widget } => { //ViewMode::EditAttachments { ref widget } => {
widget.dirty // widget.dirty
|| widget.buttons.is_dirty() // || widget.buttons.is_dirty()
|| self.dirty // || self.dirty
|| self.pager.is_dirty() // || self.pager.is_dirty()
|| self.form.is_dirty() // || self.form.is_dirty()
} //}
ViewMode::Edit => self.dirty || self.pager.is_dirty() || self.form.is_dirty(), ViewMode::Edit => self.dirty || self.pager.is_dirty() || self.form.is_dirty(),
ViewMode::Discard(_, ref widget) => { ViewMode::Discard(_, ref widget) => {
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty() widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
@ -2230,13 +2237,32 @@ impl Component for Composer {
self.dirty = value; self.dirty = value;
self.pager.set_dirty(value); self.pager.set_dirty(value);
self.form.set_dirty(value); self.form.set_dirty(value);
if let ViewMode::EditAttachments { ref mut widget } = self.mode { match self.mode {
(EditAttachmentsRefMut { ViewMode::Discard(_, ref mut widget) => {
inner: widget, widget.set_dirty(value);
draft: &mut self.draft, }
}) ViewMode::SelectRecipients(ref mut widget) => {
.set_dirty(value); widget.set_dirty(value);
}
#[cfg(feature = "gpgme")]
ViewMode::SelectEncryptKey(_, ref mut widget) => {
widget.set_dirty(value);
}
ViewMode::Send(ref mut widget) => {
widget.set_dirty(value);
}
ViewMode::WaitingForSendResult(ref mut widget, _) => {
widget.set_dirty(value);
}
ViewMode::Edit | ViewMode::Embed => {}
} }
//if let ViewMode::EditAttachments { ref mut widget } = self.mode {
// (EditAttachmentsRefMut {
// inner: widget,
// draft: &mut self.draft,
// })
// .set_dirty(value);
//}
} }
fn kill(&mut self, uuid: ComponentId, context: &mut Context) { fn kill(&mut self, uuid: ComponentId, context: &mut Context) {

View File

@ -164,7 +164,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
theme_default.fg, theme_default.fg,
bg, bg,
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 1 + i)), bottom_right!(area)), (pos_inc(area.upper_left(), (0, 1 + i)), area.bottom_right()),
None, None,
); );
} else { } else {
@ -178,7 +178,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
theme_default.fg, theme_default.fg,
bg, bg,
theme_default.attrs, theme_default.attrs,
(pos_inc(upper_left!(area), (0, 1 + i)), bottom_right!(area)), (pos_inc(area.upper_left(), (0, 1 + i)), area.bottom_right()),
None, None,
); );
} }
@ -187,8 +187,8 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
self.inner.buttons.draw( self.inner.buttons.draw(
grid, grid,
( (
pos_inc(upper_left!(area), (0, 1 + self.draft.attachments().len())), pos_inc(area.upper_left(), (0, 1 + self.draft.attachments().len())),
bottom_right!(area), area.bottom_right(),
), ),
context, context,
); );

View File

@ -86,7 +86,7 @@ impl Component for KeySelection {
KeySelection::LoadingKeys { KeySelection::LoadingKeys {
ref mut progress_spinner, ref mut progress_spinner,
.. ..
} => progress_spinner.draw(grid, center_area(area, (2, 2)), context), } => progress_spinner.draw(grid, area.center_inside((2, 2)), context),
KeySelection::Error { ref err, .. } => { KeySelection::Error { ref err, .. } => {
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.write_string( grid.write_string(
@ -94,7 +94,7 @@ impl Component for KeySelection {
theme_default.fg, theme_default.fg,
theme_default.bg, theme_default.bg,
theme_default.attrs, theme_default.attrs,
center_area(area, (15, 2)), area.center_inside((15, 2)),
Some(0), Some(0),
); );
} }

View File

@ -31,8 +31,7 @@ use std::{
use futures::future::try_join_all; use futures::future::try_join_all;
use melib::{ use melib::{
backends::EnvelopeHashBatch, mbox::MboxMetadata, utils::datetime, Address, FlagOp, backends::EnvelopeHashBatch, mbox::MboxMetadata, utils::datetime, FlagOp, UnixTimestamp,
UnixTimestamp,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
@ -40,7 +39,6 @@ use super::*;
use crate::{ use crate::{
accounts::{JobRequest, MailboxStatus}, accounts::{JobRequest, MailboxStatus},
components::ExtendShortcutsMaps, components::ExtendShortcutsMaps,
types::segment_tree::SegmentTree,
}; };
// [ref:TODO]: emoji_text_presentation_selector should be printed along with the chars // [ref:TODO]: emoji_text_presentation_selector should be printed along with the chars
@ -232,14 +230,14 @@ impl<T> RowsState<T> {
} }
} }
mod conversations; //mod conversations;
pub use self::conversations::*; //pub use self::conversations::*;
mod compact; //mod compact;
pub use self::compact::*; //pub use self::compact::*;
mod thread; //mod thread;
pub use self::thread::*; //pub use self::thread::*;
mod plain; mod plain;
pub use self::plain::*; pub use self::plain::*;
@ -284,7 +282,7 @@ pub struct ColorCache {
pub odd_highlighted_selected: ThemeAttribute, pub odd_highlighted_selected: ThemeAttribute,
pub tag_default: ThemeAttribute, pub tag_default: ThemeAttribute,
/* Conversations */ // Conversations
pub subject: ThemeAttribute, pub subject: ThemeAttribute,
pub from: ThemeAttribute, pub from: ThemeAttribute,
pub date: ThemeAttribute, pub date: ThemeAttribute,
@ -885,11 +883,11 @@ pub trait ListingTrait: Component {
#[derive(Debug)] #[derive(Debug)]
pub enum ListingComponent { pub enum ListingComponent {
Plain(Box<PlainListing>), //Compact(Box<CompactListing>),
Threaded(Box<ThreadListing>), //Conversations(Box<ConversationsListing>),
Compact(Box<CompactListing>),
Conversations(Box<ConversationsListing>),
Offline(Box<OfflineListing>), Offline(Box<OfflineListing>),
Plain(Box<PlainListing>),
//Threaded(Box<ThreadListing>),
} }
use crate::ListingComponent::*; use crate::ListingComponent::*;
@ -898,11 +896,11 @@ impl std::ops::Deref for ListingComponent {
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match &self { match &self {
Compact(ref l) => l.as_ref(), //Compact(ref l) => l.as_ref(),
Plain(ref l) => l.as_ref(), //Conversations(ref l) => l.as_ref(),
Threaded(ref l) => l.as_ref(),
Conversations(ref l) => l.as_ref(),
Offline(ref l) => l.as_ref(), Offline(ref l) => l.as_ref(),
Plain(ref l) => l.as_ref(),
//Threaded(ref l) => l.as_ref(),
} }
} }
} }
@ -910,11 +908,11 @@ impl std::ops::Deref for ListingComponent {
impl std::ops::DerefMut for ListingComponent { impl std::ops::DerefMut for ListingComponent {
fn deref_mut(&mut self) -> &mut (dyn MailListingTrait + 'static) { fn deref_mut(&mut self) -> &mut (dyn MailListingTrait + 'static) {
match self { match self {
Compact(l) => l.as_mut(), //Compact(l) => l.as_mut(),
Plain(l) => l.as_mut(), //Conversations(l) => l.as_mut(),
Threaded(l) => l.as_mut(),
Conversations(l) => l.as_mut(),
Offline(l) => l.as_mut(), Offline(l) => l.as_mut(),
Plain(l) => l.as_mut(),
//Threaded(l) => l.as_mut(),
} }
} }
} }
@ -922,11 +920,11 @@ impl std::ops::DerefMut for ListingComponent {
impl ListingComponent { impl ListingComponent {
fn id(&self) -> ComponentId { fn id(&self) -> ComponentId {
match self { match self {
Compact(l) => l.as_component().id(), //Compact(l) => l.as_component().id(),
Plain(l) => l.as_component().id(), //Conversations(l) => l.as_component().id(),
Threaded(l) => l.as_component().id(),
Conversations(l) => l.as_component().id(),
Offline(l) => l.as_component().id(), Offline(l) => l.as_component().id(),
Plain(l) => l.as_component().id(),
//Threaded(l) => l.as_component().id(),
} }
} }
} }
@ -976,7 +974,7 @@ pub struct Listing {
dirty: bool, dirty: bool,
cursor_pos: CursorPos, cursor_pos: CursorPos,
menu_cursor_pos: CursorPos, menu_cursor_pos: CursorPos,
menu_content: CellBuffer, menu: Screen<Virtual>,
menu_scrollbar_show_timer: crate::jobs::Timer, menu_scrollbar_show_timer: crate::jobs::Timer,
show_menu_scrollbar: ShowMenuScrollbar, show_menu_scrollbar: ShowMenuScrollbar,
startup_checks_rate: RateLimit, startup_checks_rate: RateLimit,
@ -999,11 +997,11 @@ pub struct Listing {
impl std::fmt::Display for Listing { impl std::fmt::Display for Listing {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.component { match self.component {
Compact(ref l) => write!(f, "{}", l), //Compact(ref l) => write!(f, "{}", l),
Plain(ref l) => write!(f, "{}", l), //Conversations(ref l) => write!(f, "{}", l),
Threaded(ref l) => write!(f, "{}", l),
Conversations(ref l) => write!(f, "{}", l),
Offline(ref l) => write!(f, "{}", l), Offline(ref l) => write!(f, "{}", l),
Plain(ref l) => write!(f, "{}", l),
//Threaded(ref l) => write!(f, "{}", l),
} }
} }
} }
@ -1013,12 +1011,7 @@ impl Component for Listing {
if !self.is_dirty() { if !self.is_dirty() {
return; return;
} }
if !is_valid_area!(area) { let total_cols = area.width();
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_cols = get_x(bottom_right) - get_x(upper_left);
let right_component_width = if self.is_menu_visible() { let right_component_width = if self.is_menu_visible() {
if self.focus == ListingFocus::Menu { if self.focus == ListingFocus::Menu {
@ -1043,18 +1036,19 @@ impl Component for Listing {
} else { } else {
total_cols total_cols
}; };
let mid = get_x(bottom_right) - right_component_width; let mid = area.width().saturating_sub(right_component_width);
if self.dirty && mid != get_x(upper_left) { if self.dirty && mid != 0 {
for i in get_y(upper_left)..=get_y(bottom_right) { let divider_area = area.nth_col(mid);
grid[(mid, i)] for row in grid.bounds_iter(divider_area) {
.set_ch(self.sidebar_divider) for c in row {
.set_fg(self.sidebar_divider_theme.fg) grid[c]
.set_bg(self.sidebar_divider_theme.bg) .set_ch(self.sidebar_divider)
.set_attrs(self.sidebar_divider_theme.attrs); .set_fg(self.sidebar_divider_theme.fg)
.set_bg(self.sidebar_divider_theme.bg)
.set_attrs(self.sidebar_divider_theme.attrs);
}
} }
context context.dirty_areas.push_back(divider_area);
.dirty_areas
.push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right))));
} }
let account_hash = self.accounts[self.cursor_pos.account].hash; let account_hash = self.accounts[self.cursor_pos.account].hash;
@ -1065,6 +1059,8 @@ impl Component for Listing {
self.component.unrealize(context); self.component.unrealize(context);
self.component = self.component =
Offline(OfflineListing::new((account_hash, MailboxHash::default()))); Offline(OfflineListing::new((account_hash, MailboxHash::default())));
self.component
.process_event(&mut UIEvent::VisibilityChange(true), context);
self.component.realize(self.id().into(), context); self.component.realize(self.id().into(), context);
} }
@ -1080,23 +1076,21 @@ impl Component for Listing {
} else if right_component_width == 0 { } else if right_component_width == 0 {
self.draw_menu(grid, area, context); self.draw_menu(grid, area, context);
} else { } else {
self.draw_menu( self.draw_menu(grid, area.take_cols(mid), context);
grid,
(upper_left, (mid.saturating_sub(1), get_y(bottom_right))),
context,
);
if context.is_online(account_hash).is_err() if context.is_online(account_hash).is_err()
&& !matches!(self.component, ListingComponent::Offline(_)) && !matches!(self.component, ListingComponent::Offline(_))
{ {
self.component.unrealize(context); self.component.unrealize(context);
self.component = self.component =
Offline(OfflineListing::new((account_hash, MailboxHash::default()))); Offline(OfflineListing::new((account_hash, MailboxHash::default())));
self.component
.process_event(&mut UIEvent::VisibilityChange(true), context);
self.component.realize(self.id().into(), context); self.component.realize(self.id().into(), context);
} }
if let Some(s) = self.status.as_mut() { if let Some(s) = self.status.as_mut() {
s.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context); s.draw(grid, area.skip_cols(mid + 1), context);
} else { } else {
let area = (set_x(upper_left, mid + 1), bottom_right); let area = area.skip_cols(mid + 1);
self.component.draw(grid, area, context); self.component.draw(grid, area, context);
if self.component.unfocused() { if self.component.unfocused() {
self.view self.view
@ -1115,14 +1109,14 @@ impl Component for Listing {
self.sidebar_divider = self.sidebar_divider =
*account_settings!(context[account_hash].listing.sidebar_divider); *account_settings!(context[account_hash].listing.sidebar_divider);
self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider"); self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider");
self.menu_content = CellBuffer::new_with_context(0, 0, None, context); self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => { UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => {
if self.show_menu_scrollbar == ShowMenuScrollbar::True { if self.show_menu_scrollbar == ShowMenuScrollbar::True {
self.show_menu_scrollbar = ShowMenuScrollbar::False; self.show_menu_scrollbar = ShowMenuScrollbar::False;
self.set_dirty(true); self.set_dirty(true);
self.menu_content.empty(); self.menu.grid_mut().empty();
} }
return true; return true;
} }
@ -1181,7 +1175,7 @@ impl Component for Listing {
}, },
}) })
.collect::<_>(); .collect::<_>();
self.menu_content.empty(); self.menu.grid_mut().empty();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(match msg { .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(match msg {
@ -1196,7 +1190,7 @@ impl Component for Listing {
.accounts .accounts
.get_index_of(account_hash) .get_index_of(account_hash)
.expect("Invalid account_hash in UIEventMailbox{Delete,Create}"); .expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
self.menu_content.empty(); self.menu.grid_mut().empty();
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts
[account_index] [account_index]
.entries .entries
@ -1245,6 +1239,8 @@ impl Component for Listing {
self.accounts[self.cursor_pos.account].entries[fallback].mailbox_hash, self.accounts[self.cursor_pos.account].entries[fallback].mailbox_hash,
)); ));
self.component.refresh_mailbox(context, true); self.component.refresh_mailbox(context, true);
self.component
.process_event(&mut UIEvent::VisibilityChange(true), context);
} }
context context
.replies .replies
@ -1271,7 +1267,9 @@ impl Component for Listing {
.process_event(&mut UIEvent::VisibilityChange(false), context); .process_event(&mut UIEvent::VisibilityChange(false), context);
self.component self.component
.set_coordinates((account_hash, *mailbox_hash)); .set_coordinates((account_hash, *mailbox_hash));
self.menu_content.empty(); self.component
.process_event(&mut UIEvent::VisibilityChange(true), context);
self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
} }
return true; return true;
@ -1292,9 +1290,10 @@ impl Component for Listing {
// Need to clear gap between sidebar and listing component, if any. // Need to clear gap between sidebar and listing component, if any.
self.dirty = true; self.dirty = true;
} }
self.set_dirty(matches!(new_value, Focus::EntryFullscreen));
} }
Some(ListingMessage::UpdateView) => { Some(ListingMessage::UpdateView) => {
log::trace!("UpdateView"); self.view.set_dirty(true);
} }
Some(ListingMessage::OpenEntryUnderCursor { Some(ListingMessage::OpenEntryUnderCursor {
env_hash, env_hash,
@ -1844,7 +1843,7 @@ impl Component for Listing {
{ {
target.collapsed = !(target.collapsed); target.collapsed = !(target.collapsed);
self.dirty = true; self.dirty = true;
self.menu_content.empty(); self.menu.grid_mut().empty();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
@ -1932,7 +1931,7 @@ impl Component for Listing {
} else if shortcut!(k == shortcuts[Shortcuts::LISTING]["scroll_down"]) { } else if shortcut!(k == shortcuts[Shortcuts::LISTING]["scroll_down"]) {
while amount > 0 { while amount > 0 {
match self.menu_cursor_pos { match self.menu_cursor_pos {
/* If current account has mailboxes, go to first mailbox */ // If current account has mailboxes, go to first mailbox
CursorPos { CursorPos {
ref account, ref account,
ref mut menu, ref mut menu,
@ -1941,7 +1940,7 @@ impl Component for Listing {
{ {
*menu = MenuEntryCursor::Mailbox(0); *menu = MenuEntryCursor::Mailbox(0);
} }
/* If current account has no mailboxes, go to next account */ // If current account has no mailboxes, go to next account
CursorPos { CursorPos {
ref mut account, ref mut account,
ref mut menu, ref mut menu,
@ -1951,8 +1950,8 @@ impl Component for Listing {
*account += 1; *account += 1;
*menu = MenuEntryCursor::Status; *menu = MenuEntryCursor::Status;
} }
/* If current account has no mailboxes and there is no next // If current account has no mailboxes and there is no next account,
* account, return true */ // return true
CursorPos { CursorPos {
menu: MenuEntryCursor::Status, menu: MenuEntryCursor::Status,
.. ..
@ -1985,7 +1984,7 @@ impl Component for Listing {
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
@ -2046,7 +2045,7 @@ impl Component for Listing {
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu.grid_mut().empty();
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
@ -2107,7 +2106,7 @@ impl Component for Listing {
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
return true; return true;
@ -2133,7 +2132,7 @@ impl Component for Listing {
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
@ -2175,7 +2174,7 @@ impl Component for Listing {
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
} }
self.menu_content.empty(); self.menu.grid_mut().empty();
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
@ -2192,18 +2191,18 @@ impl Component for Listing {
return true; return true;
} }
UIEvent::Action(Action::Tab(ManageMailboxes)) => { UIEvent::Action(Action::Tab(ManageMailboxes)) => {
let account_pos = self.cursor_pos.account; //let account_pos = self.cursor_pos.account;
let mgr = MailboxManager::new(context, account_pos); //let mgr = MailboxManager::new(context, account_pos);
context //context
.replies // .replies
.push_back(UIEvent::Action(Tab(New(Some(Box::new(mgr)))))); // .push_back(UIEvent::Action(Tab(New(Some(Box::new(mgr))))));
return true; return true;
} }
UIEvent::Action(Action::Tab(ManageJobs)) => { UIEvent::Action(Action::Tab(ManageJobs)) => {
let mgr = JobManager::new(context); //let mgr = JobManager::new(context);
context //context
.replies // .replies
.push_back(UIEvent::Action(Tab(New(Some(Box::new(mgr)))))); // .push_back(UIEvent::Action(Tab(New(Some(Box::new(mgr))))));
return true; return true;
} }
UIEvent::Action(Action::Compose(ComposeAction::Mailto(ref mailto))) => { UIEvent::Action(Action::Compose(ComposeAction::Mailto(ref mailto))) => {
@ -2221,8 +2220,8 @@ impl Component for Listing {
| UIEvent::EnvelopeRename(_, _) | UIEvent::EnvelopeRename(_, _)
| UIEvent::EnvelopeRemove(_, _) => { | UIEvent::EnvelopeRemove(_, _) => {
self.dirty = true; self.dirty = true;
/* clear menu to force redraw */ // clear menu to force redraw
self.menu_content.empty(); self.menu.grid_mut().empty();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -2355,11 +2354,11 @@ impl Component for Listing {
ret.insert( ret.insert(
self.component.id(), self.component.id(),
match &self.component { match &self.component {
Compact(l) => l.as_component(), //Compact(l) => l.as_component(),
Plain(l) => l.as_component(), //Conversations(l) => l.as_component(),
Threaded(l) => l.as_component(),
Conversations(l) => l.as_component(),
Offline(l) => l.as_component(), Offline(l) => l.as_component(),
Plain(l) => l.as_component(),
//Threaded(l) => l.as_component(),
}, },
); );
@ -2371,11 +2370,11 @@ impl Component for Listing {
ret.insert( ret.insert(
self.component.id(), self.component.id(),
match &mut self.component { match &mut self.component {
Compact(l) => l.as_component_mut(), //Compact(l) => l.as_component_mut(),
Plain(l) => l.as_component_mut(), //Conversations(l) => l.as_component_mut(),
Threaded(l) => l.as_component_mut(),
Conversations(l) => l.as_component_mut(),
Offline(l) => l.as_component_mut(), Offline(l) => l.as_component_mut(),
Plain(l) => l.as_component_mut(),
//Threaded(l) => l.as_component_mut(),
}, },
); );
@ -2430,7 +2429,7 @@ impl Listing {
account: 0, account: 0,
menu: MenuEntryCursor::Mailbox(0), menu: MenuEntryCursor::Mailbox(0),
}, },
menu_content: CellBuffer::new_with_context(0, 0, None, context), menu: Screen::<Virtual>::new(),
menu_scrollbar_show_timer: context.main_loop_handler.job_executor.clone().create_timer( menu_scrollbar_show_timer: context.main_loop_handler.job_executor.clone().create_timer(
std::time::Duration::from_secs(0), std::time::Duration::from_secs(0),
std::time::Duration::from_millis(1200), std::time::Duration::from_millis(1200),
@ -2469,27 +2468,23 @@ impl Listing {
.iter() .iter()
.map(|entry| entry.entries.len() + 1) .map(|entry| entry.entries.len() + 1)
.sum::<usize>(); .sum::<usize>();
let min_width: usize = 2 * width!(area); let min_width: usize = 2 * area.width();
let (width, height) = self.menu_content.size(); let (width, height) = self.menu.grid().size();
let cursor = match self.focus { let cursor = match self.focus {
ListingFocus::Mailbox => self.cursor_pos, ListingFocus::Mailbox => self.cursor_pos,
ListingFocus::Menu => self.menu_cursor_pos, ListingFocus::Menu => self.menu_cursor_pos,
}; };
if min_width > width || height < total_height || self.dirty { if min_width > width || height < total_height || self.dirty {
let _ = self.menu_content.resize(min_width * 2, total_height, None); let _ = self.menu.resize(min_width * 2, total_height);
let bottom_right = pos_dec(self.menu_content.size(), (1, 1));
let mut y = 0; let mut y = 0;
for a in 0..self.accounts.len() { for a in 0..self.accounts.len() {
if y > get_y(bottom_right) { let menu_area = self.menu.area().skip_rows(y);
break; y += self.print_account(menu_area, a, context);
}
y += self.print_account(((0, y), bottom_right), a, context);
y += 3; y += 3;
} }
} }
let rows = height!(area); let rows = area.height();
let (width, height) = self.menu_content.size();
const SCROLLING_CONTEXT: usize = 3; const SCROLLING_CONTEXT: usize = 3;
let y_offset = (cursor.account) let y_offset = (cursor.account)
+ self + self
@ -2510,15 +2505,12 @@ impl Listing {
}; };
grid.copy_area( grid.copy_area(
&self.menu_content, self.menu.grid(),
area, area,
( self.menu
( .area()
0, .skip_rows(skip_offset.min((self.menu.area().height() - 1).saturating_sub(rows)))
std::cmp::min((height - 1).saturating_sub(rows), skip_offset), .take_rows((skip_offset + rows).min(self.menu.area().height() - 1)),
),
(width - 1, std::cmp::min(skip_offset + rows, height - 1)),
),
); );
if self.show_menu_scrollbar == ShowMenuScrollbar::True && total_height > rows { if self.show_menu_scrollbar == ShowMenuScrollbar::True && total_height > rows {
if self.focus == ListingFocus::Menu { if self.focus == ListingFocus::Menu {
@ -2537,16 +2529,13 @@ impl Listing {
} }
ScrollBar::default().set_show_arrows(true).draw( ScrollBar::default().set_show_arrows(true).draw(
grid, grid,
( area.nth_col(area.width().saturating_sub(1)),
pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
bottom_right!(area),
),
context, context,
/* position */ // position
skip_offset, skip_offset,
/* visible_rows */ // visible_rows
rows, rows,
/* length */ // length
total_height, total_height,
); );
} else if total_height < rows { } else if total_height < rows {
@ -2560,12 +2549,9 @@ impl Listing {
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
/* /// Print a single account in the menu area.
* Print a single account in the menu area. fn print_account(&mut self, mut area: Area, aidx: usize, context: &mut Context) -> usize {
*/ let account_y = self.menu.area().height() - area.height();
fn print_account(&mut self, area: Area, aidx: usize, context: &mut Context) -> usize {
debug_assert!(is_valid_area!(area));
#[derive(Copy, Debug, Clone)] #[derive(Copy, Debug, Clone)]
struct Line { struct Line {
collapsed: bool, collapsed: bool,
@ -2584,9 +2570,6 @@ impl Listing {
.map(|(&hash, entry)| (hash, entry.ref_mailbox.clone())) .map(|(&hash, entry)| (hash, entry.ref_mailbox.clone()))
.collect(); .collect();
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let cursor = match self.focus { let cursor = match self.focus {
ListingFocus::Mailbox => self.cursor_pos, ListingFocus::Mailbox => self.cursor_pos,
ListingFocus::Menu => self.menu_cursor_pos, ListingFocus::Menu => self.menu_cursor_pos,
@ -2652,8 +2635,8 @@ impl Listing {
crate::conf::value(context, "mail.sidebar_account_name") crate::conf::value(context, "mail.sidebar_account_name")
}; };
/* Print account name first */ // Print account name first
self.menu_content.write_string( self.menu.grid_mut().write_string(
&self.accounts[aidx].name, &self.accounts[aidx].name,
account_attrs.fg, account_attrs.fg,
account_attrs.bg, account_attrs.bg,
@ -2661,18 +2644,20 @@ impl Listing {
area, area,
None, None,
); );
area = self.menu.area().skip_rows(account_y);
if lines.is_empty() { if lines.is_empty() {
self.menu_content.write_string( self.menu.grid_mut().write_string(
"offline", "offline",
crate::conf::value(context, "error_message").fg, crate::conf::value(context, "error_message").fg,
account_attrs.bg, account_attrs.bg,
account_attrs.attrs, account_attrs.attrs,
(pos_inc(upper_left, (0, 1)), bottom_right), area.skip_rows(1),
None, None,
); );
return 0; return 0;
} }
area = self.menu.area().skip_rows(account_y);
let lines_len = lines.len(); let lines_len = lines.len();
let mut idx = 0; let mut idx = 0;
@ -2683,7 +2668,7 @@ impl Listing {
// are not visible. // are not visible.
let mut skip: Option<usize> = None; let mut skip: Option<usize> = None;
let mut skipped_counter: usize = 0; let mut skipped_counter: usize = 0;
'grid_loop: for y in get_y(upper_left) + 1..get_y(bottom_right) { 'grid_loop: for y in 0..area.height() {
if idx == lines_len { if idx == lines_len {
break; break;
} }
@ -2751,14 +2736,13 @@ impl Listing {
) )
}; };
/* Calculate how many columns the mailbox index tags should occupy with right // Calculate how many columns the mailbox index tags should occupy with right
* alignment, eg. // alignment, eg.
* 1 // 1
* 2 // 2
* ... // ...
* 9 // 9
* 10 // 10
*/
let total_mailbox_no_digits = { let total_mailbox_no_digits = {
let mut len = lines_len; let mut len = lines_len;
let mut ctr = 1; let mut ctr = 1;
@ -2804,7 +2788,7 @@ impl Listing {
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or(" "); .unwrap_or(" ");
let (x, _) = self.menu_content.write_string( let (x, _) = self.menu.grid_mut().write_string(
&if *account_settings!( &if *account_settings!(
context[self.accounts[aidx].hash] context[self.accounts[aidx].hash]
.listing .listing
@ -2822,9 +2806,10 @@ impl Listing {
index_att.fg, index_att.fg,
index_att.bg, index_att.bg,
index_att.attrs, index_att.attrs,
(set_y(upper_left, y), bottom_right), area.nth_row(y + 1),
None, None,
); );
area = self.menu.area().skip_rows(account_y);
{ {
branches.clear(); branches.clear();
branches.push_str(no_sibling_str); branches.push_str(no_sibling_str);
@ -2846,24 +2831,37 @@ impl Listing {
} }
} }
} }
let (x, _) = self.menu_content.write_string( let x = self
&branches, .menu
att.fg, .grid_mut()
att.bg, .write_string(
att.attrs, &branches,
((x, y), bottom_right), att.fg,
None, att.bg,
); att.attrs,
let (x, _) = self.menu_content.write_string( area.nth_row(y + 1).skip_cols(x),
context.accounts[self.accounts[aidx].index].mailbox_entries[&l.mailbox_idx].name(), None,
att.fg, )
att.bg, .0
att.attrs, + x;
((x, y), bottom_right), area = self.menu.area().skip_rows(account_y);
None, let x = self
); .menu
.grid_mut()
.write_string(
context.accounts[self.accounts[aidx].index].mailbox_entries[&l.mailbox_idx]
.name(),
att.fg,
att.bg,
att.attrs,
area.nth_row(y + 1).skip_cols(x),
None,
)
.0
+ x;
area = self.menu.area().skip_rows(account_y);
/* Unread message count */ // Unread message count
let count_string = match (l.count, l.collapsed_count) { let count_string = match (l.count, l.collapsed_count) {
(None, None) => " ...".to_string(), (None, None) => " ...".to_string(),
(Some(0), None) => String::new(), (Some(0), None) => String::new(),
@ -2875,28 +2873,28 @@ impl Listing {
(None, Some(coll)) => format!(" ({}) v", coll), (None, Some(coll)) => format!(" ({}) v", coll),
}; };
let (x, _) = self.menu_content.write_string( let x = self
&count_string, .menu
unread_count_att.fg, .grid_mut()
unread_count_att.bg, .write_string(
unread_count_att.attrs &count_string,
| if l.count.unwrap_or(0) > 0 { unread_count_att.fg,
Attr::BOLD unread_count_att.bg,
} else { unread_count_att.attrs
Attr::DEFAULT | if l.count.unwrap_or(0) > 0 {
}, Attr::BOLD
( } else {
( Attr::DEFAULT
/* Hide part of mailbox name if need be to fit the message count */ },
std::cmp::min(x, get_x(bottom_right).saturating_sub(count_string.len())), area.nth_row(y + 1)
y, .skip_cols(x.min(area.width().saturating_sub(count_string.len()))),
), None,
bottom_right, )
), .0
None, + x.min(area.width().saturating_sub(count_string.len()));
); area = self.menu.area().skip_rows(account_y);
for c in self.menu_content.row_iter(x..(get_x(bottom_right) + 1), y) { for c in self.menu.grid_mut().row_iter(area, x..area.width(), y + 1) {
self.menu_content[c] self.menu.grid_mut()[c]
.set_fg(att.fg) .set_fg(att.fg)
.set_bg(att.bg) .set_bg(att.bg)
.set_attrs(att.attrs); .set_attrs(att.attrs);
@ -2958,7 +2956,6 @@ impl Listing {
self.component self.component
.set_coordinates((account_hash, *mailbox_hash)); .set_coordinates((account_hash, *mailbox_hash));
self.component.refresh_mailbox(context, true); self.component.refresh_mailbox(context, true);
/* Check if per-mailbox configuration overrides general configuration */
let index_style = let index_style =
mailbox_settings!(context[account_hash][mailbox_hash].listing.index_style); mailbox_settings!(context[account_hash][mailbox_hash].listing.index_style);
@ -2969,6 +2966,8 @@ impl Listing {
Offline(OfflineListing::new((account_hash, MailboxHash::default()))); Offline(OfflineListing::new((account_hash, MailboxHash::default())));
self.component.realize(self.id().into(), context); self.component.realize(self.id().into(), context);
} }
self.component
.process_event(&mut UIEvent::VisibilityChange(true), context);
self.status = None; self.status = None;
context context
.replies .replies
@ -2983,8 +2982,8 @@ impl Listing {
self.sidebar_divider = *account_settings!(context[account_hash].listing.sidebar_divider); self.sidebar_divider = *account_settings!(context[account_hash].listing.sidebar_divider);
self.set_dirty(true); self.set_dirty(true);
self.menu_cursor_pos = self.cursor_pos; self.menu_cursor_pos = self.cursor_pos;
/* clear menu to force redraw */ // clear menu to force redraw
self.menu_content.empty(); self.menu.grid_mut().empty();
if *account_settings!(context[account_hash].listing.show_menu_scrollbar) { if *account_settings!(context[account_hash].listing.show_menu_scrollbar) {
self.show_menu_scrollbar = ShowMenuScrollbar::True; self.show_menu_scrollbar = ShowMenuScrollbar::True;
self.menu_scrollbar_show_timer.rearm(); self.menu_scrollbar_show_timer.rearm();
@ -2995,7 +2994,7 @@ impl Listing {
fn open_status(&mut self, account_idx: usize, context: &mut Context) { fn open_status(&mut self, account_idx: usize, context: &mut Context) {
self.status = Some(AccountStatus::new(account_idx, self.theme_default)); self.status = Some(AccountStatus::new(account_idx, self.theme_default));
self.menu_content.empty(); self.menu.grid_mut().empty();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -3007,51 +3006,61 @@ impl Listing {
!matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility !matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility
} }
fn set_style(&mut self, new_style: IndexStyle, context: &mut Context) { fn set_style(&mut self, _new_style: IndexStyle, context: &mut Context) {
let old = match new_style { if matches!(self.component, Plain(_)) {
IndexStyle::Plain => { return;
if matches!(self.component, Plain(_)) { }
return; let coordinates = self.component.coordinates();
} self.component = Plain(PlainListing::new(self.id, coordinates));
let coordinates = self.component.coordinates(); self.component
std::mem::replace( .process_event(&mut UIEvent::VisibilityChange(true), context);
&mut self.component, //let old = match new_style {
Plain(PlainListing::new(self.id, coordinates)), // IndexStyle::Plain => {
) // if matches!(self.component, Plain(_)) {
} // return;
IndexStyle::Threaded => { // }
if matches!(self.component, Threaded(_)) { // let coordinates = self.component.coordinates();
return; // std::mem::replace(
} // &mut self.component,
let coordinates = self.component.coordinates(); // Plain(PlainListing::new(self.id, coordinates)),
std::mem::replace( // )
&mut self.component, // }
Threaded(ThreadListing::new(self.id, coordinates, context)), // IndexStyle::Threaded => {
) // return;
} // //if matches!(self.component, Threaded(_)) {
IndexStyle::Compact => { // // return;
if matches!(self.component, Compact(_)) { // //}
return; // //let coordinates = self.component.coordinates();
} // //std::mem::replace(
let coordinates = self.component.coordinates(); // // &mut self.component,
std::mem::replace( // // Threaded(ThreadListing::new(self.id, coordinates,
&mut self.component, // context)), //)
Compact(CompactListing::new(self.id, coordinates)), // }
) // IndexStyle::Compact => {
} // return;
IndexStyle::Conversations => { // //if matches!(self.component, Compact(_)) {
if matches!(self.component, Conversations(_)) { // // return;
return; // //}
} // //let coordinates = self.component.coordinates();
let coordinates = self.component.coordinates(); // //std::mem::replace(
std::mem::replace( // // &mut self.component,
&mut self.component, // // Compact(CompactListing::new(self.id, coordinates)),
Conversations(ConversationsListing::new(self.id, coordinates)), // //)
) // }
} // IndexStyle::Conversations => {
}; // return;
old.unrealize(context); // //if matches!(self.component, Conversations(_)) {
self.component.realize(self.id.into(), context); // // return;
// //}
// //let coordinates = self.component.coordinates();
// //std::mem::replace(
// // &mut self.component,
// // Conversations(ConversationsListing::new(self.id,
// coordinates)), //)
// }
//};
//old.unrealize(context);
//self.component.realize(self.id.into(), context);
} }
} }

View File

@ -286,17 +286,19 @@ impl MailListingTrait for CompactListing {
Err(_) => { Err(_) => {
let message: String = let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status(); context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] = self.data_columns.columns[0].resize_with_context(message.len(), 1, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0; self.length = 0;
self.data_columns.columns[0].write_string( {
message.as_str(), let area = self.data_columns.columns[0].area();
self.color_cache.theme_default.fg, self.data_columns.columns[0].grid_mut().write_string(
self.color_cache.theme_default.bg, message.as_str(),
self.color_cache.theme_default.attrs, self.color_cache.theme_default.fg,
((0, 0), (message.len() - 1, 0)), self.color_cache.theme_default.bg,
None, self.color_cache.theme_default.attrs,
); area,
None,
)
};
return; return;
} }
} }
@ -550,21 +552,17 @@ impl MailListingTrait for CompactListing {
.set_even_odd_theme(self.color_cache.even, self.color_cache.odd); .set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
/* index column */ /* index column */
self.data_columns.columns[0] = self.data_columns.columns[0].resize_with_context(min_width.0, self.rows.len(), context);
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
self.data_columns.segment_tree[0] = row_widths.0.into(); self.data_columns.segment_tree[0] = row_widths.0.into();
/* date column */ /* date column */
self.data_columns.columns[1] = self.data_columns.columns[1].resize_with_context(min_width.1, self.rows.len(), context);
CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
self.data_columns.segment_tree[1] = row_widths.1.into(); self.data_columns.segment_tree[1] = row_widths.1.into();
/* from column */ /* from column */
self.data_columns.columns[2] = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context);
CellBuffer::new_with_context(min_width.2, self.rows.len(), None, context);
self.data_columns.segment_tree[2] = row_widths.2.into(); self.data_columns.segment_tree[2] = row_widths.2.into();
/* subject column */ /* subject column */
self.data_columns.columns[3] = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context);
CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
self.data_columns.segment_tree[3] = row_widths.3.into(); self.data_columns.segment_tree[3] = row_widths.3.into();
self.rows_drawn = SegmentTree::from( self.rows_drawn = SegmentTree::from(
@ -580,16 +578,18 @@ impl MailListingTrait for CompactListing {
); );
if self.length == 0 && self.filter_term.is_empty() { if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status(); let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] = self.data_columns.columns[0].resize_with_context(message.len(), 1, context);
CellBuffer::new_with_context(message.len(), self.length + 1, None, context); {
self.data_columns.columns[0].write_string( let area = self.data_columns.columns[0].area();
&message, self.data_columns.columns[0].grid_mut().write_string(
self.color_cache.theme_default.fg, &message,
self.color_cache.theme_default.bg, self.color_cache.theme_default.fg,
self.color_cache.theme_default.attrs, self.color_cache.theme_default.bg,
((0, 0), (message.len() - 1, 0)), self.color_cache.theme_default.attrs,
None, area,
); None,
)
};
} }
} }
} }
@ -663,17 +663,12 @@ impl ListingTrait for CompactListing {
self.cursor_pos.2 == idx, self.cursor_pos.2 == idx,
self.rows.is_thread_selected(thread_hash) self.rows.is_thread_selected(thread_hash)
); );
let (upper_left, bottom_right) = area; let x = self.data_columns.widths[0]
let x = get_x(upper_left)
+ self.data_columns.widths[0]
+ self.data_columns.widths[1] + self.data_columns.widths[1]
+ self.data_columns.widths[2] + self.data_columns.widths[2]
+ 3 * 2; + 3 * 2;
for c in grid.row_iter( for c in grid.row_iter(area, 0..area.width(), 0) {
get_x(upper_left)..(get_x(bottom_right) + 1),
get_y(upper_left),
) {
grid[c] grid[c]
.set_fg(row_attr.fg) .set_fg(row_attr.fg)
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
@ -681,20 +676,11 @@ impl ListingTrait for CompactListing {
} }
grid.copy_area( grid.copy_area(
&self.data_columns.columns[3], self.data_columns.columns[3].grid(),
(set_x(upper_left, x), bottom_right), area.skip_cols(x),
( self.data_columns.columns[3].area().nth_row(idx),
(0, idx),
pos_dec(
(
self.data_columns.widths[3],
self.data_columns.columns[3].size().1,
),
(1, 1),
),
),
); );
for c in grid.row_iter(x..(get_x(bottom_right) + 1), get_y(upper_left)) { for c in grid.row_iter(area, x..area.width(), 0) {
grid[c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); grid[c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
} }
} }
@ -705,20 +691,20 @@ impl ListingTrait for CompactListing {
{ {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
} }
let upper_left = upper_left!(area); let upper_left = area.upper_left();
let bottom_right = bottom_right!(area); let bottom_right = area.bottom_right();
if self.length == 0 { if self.length == 0 {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
grid.copy_area( grid.copy_area(
&self.data_columns.columns[0], self.data_columns.columns[0].grid(),
area, area,
((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))), self.data_columns.columns[0].area(),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
return; return;
} }
let rows = get_y(bottom_right) - get_y(upper_left) + 1; let rows = area.height();
if rows == 0 { if rows == 0 {
return; return;
} }
@ -773,7 +759,7 @@ impl ListingTrait for CompactListing {
if idx >= self.length { if idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = nth_row_area(area, idx % rows); let new_area = area.nth_row(idx % rows);
self.data_columns self.data_columns
.draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area)); .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
if highlight { if highlight {
@ -798,15 +784,15 @@ impl ListingTrait for CompactListing {
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
_ = self _ = self
.data_columns .data_columns
.recalc_widths((width!(area), height!(area)), top_idx); .recalc_widths((area.width(), area.height()), top_idx);
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
/* copy table columns */ /* copy table columns */
self.data_columns self.data_columns
.draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area)); .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
/* apply each row colors separately */ /* apply each row colors separately */
for i in top_idx..(top_idx + height!(area)) { for i in top_idx..(top_idx + area.height()) {
if let Some(row_attr) = self.rows.row_attr_cache.get(&i) { if let Some(row_attr) = self.rows.row_attr_cache.get(&i) {
grid.change_theme(nth_row_area(area, i % rows), *row_attr); grid.change_theme(area.nth_row(i % rows), *row_attr);
} }
} }
@ -818,12 +804,12 @@ impl ListingTrait for CompactListing {
true, true,
false false
); );
grid.change_theme(nth_row_area(area, self.cursor_pos.2 % rows), row_attr); grid.change_theme(area.nth_row(self.cursor_pos.2 % rows), row_attr);
/* clear gap if available height is more than count of entries */ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length { if top_idx + rows > self.length {
grid.clear_area( grid.clear_area(
(pos_inc(upper_left, (0, rows - 1)), bottom_right), area.skip_rows(top_idx + rows - self.length),
self.color_cache.theme_default, self.color_cache.theme_default,
); );
} }
@ -870,7 +856,7 @@ impl ListingTrait for CompactListing {
self.new_cursor_pos.2 = self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2); std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else { } else {
self.data_columns.columns[0] = CellBuffer::new_with_context(0, 0, None, context); self.data_columns.columns[0].resize_with_context(0, 0, context);
} }
self.redraw_threads_list( self.redraw_threads_list(
context, context,
@ -1228,97 +1214,155 @@ impl CompactListing {
drop(envelope); drop(envelope);
let columns = &mut self.data_columns.columns; let columns = &mut self.data_columns.columns;
let min_width = ( let min_width = (
columns[0].size().0, columns[0].area().width(),
columns[1].size().0, columns[1].area().width(),
columns[2].size().0, columns[2].area().width(),
columns[3].size().0, columns[3].area().width(),
); );
let (x, _) = columns[0].write_string( let (x, _) = {
&idx.to_string(), let area = columns[0].area().nth_row(idx);
row_attr.fg, columns[0].grid_mut().write_string(
row_attr.bg, &idx.to_string(),
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.0, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[0].row_iter(x..min_width.0, idx) { None,
columns[0][c].set_bg(row_attr.bg).set_ch(' '); )
};
for c in {
let area = columns[0].area();
columns[0].grid_mut().row_iter(area, x..min_width.0, idx)
} {
columns[0].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs)
.set_ch(' ');
} }
let (x, _) = columns[1].write_string( let (x, _) = {
&strings.date, let area = columns[1].area().nth_row(idx);
row_attr.fg, columns[1].grid_mut().write_string(
row_attr.bg, &strings.date,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.1.saturating_sub(1), idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[1].row_iter(x..min_width.1, idx) { None,
columns[1][c].set_bg(row_attr.bg).set_ch(' '); )
};
for c in {
let area = columns[1].area();
columns[1].grid_mut().row_iter(area, x..min_width.1, idx)
} {
columns[1].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs)
.set_ch(' ');
} }
let (x, _) = columns[2].write_string( let (x, _) = {
&strings.from, let area = columns[2].area().nth_row(idx);
row_attr.fg, columns[2].grid_mut().write_string(
row_attr.bg, &strings.from,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.2, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[2].row_iter(x..min_width.2, idx) { None,
columns[2][c].set_bg(row_attr.bg).set_ch(' '); )
};
for c in {
let area = columns[2].area();
columns[2].grid_mut().row_iter(area, x..min_width.2, idx)
} {
columns[2].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs)
.set_ch(' ');
} }
let (x, _) = columns[3].write_string( let (x, _) = {
&strings.flag, let area = columns[3].area().nth_row(idx);
row_attr.fg, columns[3].grid_mut().write_string(
row_attr.bg, &strings.flag,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.3, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
let (x, _) = columns[3].write_string( None,
&strings.subject, )
row_attr.fg, };
row_attr.bg, let (x, _) = {
row_attr.attrs, let area = columns[3].area().nth_row(idx).skip_cols(x);
((x, idx), (min_width.3, idx)), columns[3].grid_mut().write_string(
None, &strings.subject,
); row_attr.fg,
if let Some(c) = columns[3].get_mut(x, idx) { row_attr.bg,
c.set_bg(row_attr.bg).set_ch(' '); row_attr.attrs,
area,
None,
)
};
if let Some(c) = columns[3].grid_mut().get_mut(x, idx) {
c.set_bg(row_attr.bg).set_attrs(row_attr.attrs).set_ch(' ');
} }
let x = { let x = {
let mut x = x + 1; let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let color = color.unwrap_or(self.color_cache.tag_default.bg); let color = color.unwrap_or(self.color_cache.tag_default.bg);
let (_x, _) = columns[3].write_string( let _x = {
t, let area = columns[3].area().nth_row(idx).skip_cols(x + 1);
self.color_cache.tag_default.fg, columns[3]
color, .grid_mut()
self.color_cache.tag_default.attrs, .write_string(
((x + 1, idx), (min_width.3, idx)), t,
None, self.color_cache.tag_default.fg,
); color,
for c in columns[3].row_iter(x..(x + 1), idx) { self.color_cache.tag_default.attrs,
columns[3][c].set_bg(color); area,
None,
)
.0
+ x
+ 1
};
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color);
} }
for c in columns[3].row_iter(_x..(_x + 1), idx) { for c in {
columns[3][c].set_bg(color).set_keep_bg(true); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, _x..(_x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color).set_keep_bg(true);
} }
for c in columns[3].row_iter((x + 1)..(_x + 1), idx) { for c in {
columns[3][c] let area = columns[3].area();
columns[3].grid_mut().row_iter(area, (x + 1)..(_x + 1), idx)
} {
columns[3].grid_mut()[c]
.set_keep_fg(true) .set_keep_fg(true)
.set_keep_bg(true) .set_keep_bg(true)
.set_keep_attrs(true); .set_keep_attrs(true);
} }
for c in columns[3].row_iter(x..(x + 1), idx) { for c in {
columns[3][c].set_keep_bg(true); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_keep_bg(true);
} }
x = _x + 1; x = _x + 2;
columns[3][(x, idx)].set_bg(row_attr.bg).set_ch(' ');
} }
x x
}; };
for c in columns[3].row_iter(x..min_width.3, idx) { for c in {
columns[3][c].set_ch(' ').set_bg(row_attr.bg); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..min_width.3, idx)
} {
columns[3].grid_mut()[c]
.set_ch(' ')
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
} }
*self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings); *self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings);
self.rows_drawn.update(idx, 1); self.rows_drawn.update(idx, 1);
@ -1336,10 +1380,10 @@ impl CompactListing {
self.rows_drawn.update(i, 0); self.rows_drawn.update(i, 0);
} }
let min_width = ( let min_width = (
self.data_columns.columns[0].size().0, self.data_columns.columns[0].area().width(),
self.data_columns.columns[1].size().0, self.data_columns.columns[1].area().width(),
self.data_columns.columns[2].size().0, self.data_columns.columns[2].area().width(),
self.data_columns.columns[3].size().0, self.data_columns.columns[3].area().width(),
); );
for (idx, ((_thread_hash, root_env_hash), strings)) in self for (idx, ((_thread_hash, root_env_hash), strings)) in self
@ -1362,76 +1406,103 @@ impl CompactListing {
panic!(); panic!();
} }
let row_attr = self.rows.row_attr_cache[&idx]; let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = self.data_columns.columns[0].write_string( let (x, _) = {
&idx.to_string(), let area = self.data_columns.columns[0].area().nth_row(idx);
row_attr.fg, self.data_columns.columns[0].grid_mut().write_string(
row_attr.bg, &idx.to_string(),
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.0, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
None,
)
};
for x in x..min_width.0 { for x in x..min_width.0 {
self.data_columns.columns[0][(x, idx)] self.data_columns.columns[0].grid_mut()[(x, idx)]
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
.set_attrs(row_attr.attrs); .set_attrs(row_attr.attrs);
} }
let (x, _) = self.data_columns.columns[1].write_string( let (x, _) = {
&strings.date, let area = self.data_columns.columns[1].area().nth_row(idx);
row_attr.fg, self.data_columns.columns[1].grid_mut().write_string(
row_attr.bg, &strings.date,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.1, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
None,
)
};
for x in x..min_width.1 { for x in x..min_width.1 {
self.data_columns.columns[1][(x, idx)] self.data_columns.columns[1].grid_mut()[(x, idx)]
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
.set_attrs(row_attr.attrs); .set_attrs(row_attr.attrs);
} }
let (x, _) = self.data_columns.columns[2].write_string( let (x, _) = {
&strings.from, let area = self.data_columns.columns[2].area().nth_row(idx);
row_attr.fg, self.data_columns.columns[2].grid_mut().write_string(
row_attr.bg, &strings.from,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.2, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
None,
)
};
#[cfg(feature = "regexp")] #[cfg(feature = "regexp")]
{ {
for text_formatter in crate::conf::text_format_regexps(context, "listing.from") { for text_formatter in crate::conf::text_format_regexps(context, "listing.from") {
let t = self.data_columns.columns[2].insert_tag(text_formatter.tag); let t = self.data_columns.columns[2]
.grid_mut()
.insert_tag(text_formatter.tag);
for (start, end) in text_formatter.regexp.find_iter(strings.from.as_str()) { for (start, end) in text_formatter.regexp.find_iter(strings.from.as_str()) {
self.data_columns.columns[2].set_tag(t, (start, idx), (end, idx)); self.data_columns.columns[2].grid_mut().set_tag(
t,
(start, idx),
(end, idx),
);
} }
} }
} }
for x in x..min_width.2 { for x in x..min_width.2 {
self.data_columns.columns[2][(x, idx)] self.data_columns.columns[2].grid_mut()[(x, idx)]
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
.set_attrs(row_attr.attrs); .set_attrs(row_attr.attrs);
} }
let (x, _) = self.data_columns.columns[3].write_string( let (x, _) = {
&strings.flag, let area = self.data_columns.columns[3].area().nth_row(idx);
row_attr.fg, self.data_columns.columns[3].grid_mut().write_string(
row_attr.bg, &strings.flag,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.3, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
let (x, _) = self.data_columns.columns[3].write_string( None,
)
};
let (x, _) = self.data_columns.columns[3].grid_mut().write_string(
&strings.subject, &strings.subject,
row_attr.fg, row_attr.fg,
row_attr.bg, row_attr.bg,
row_attr.attrs, row_attr.attrs,
((x, idx), (min_width.3, idx)), self.data_columns.columns[3]
.area()
.nth_row(idx)
.skip_cols(x),
None, None,
); );
#[cfg(feature = "regexp")] #[cfg(feature = "regexp")]
{ {
for text_formatter in crate::conf::text_format_regexps(context, "listing.subject") { for text_formatter in crate::conf::text_format_regexps(context, "listing.subject") {
let t = self.data_columns.columns[3].insert_tag(text_formatter.tag); let t = self.data_columns.columns[3]
.grid_mut()
.insert_tag(text_formatter.tag);
for (start, end) in text_formatter.regexp.find_iter(strings.subject.as_str()) { for (start, end) in text_formatter.regexp.find_iter(strings.subject.as_str()) {
self.data_columns.columns[3].set_tag(t, (start, idx), (end, idx)); self.data_columns.columns[3].grid_mut().set_tag(
t,
(start, idx),
(end, idx),
);
} }
} }
} }
@ -1439,33 +1510,36 @@ impl CompactListing {
let mut x = x + 1; let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let color = color.unwrap_or(self.color_cache.tag_default.bg); let color = color.unwrap_or(self.color_cache.tag_default.bg);
let (_x, _) = self.data_columns.columns[3].write_string( let (_x, _) = self.data_columns.columns[3].grid_mut().write_string(
t, t,
self.color_cache.tag_default.fg, self.color_cache.tag_default.fg,
color, color,
self.color_cache.tag_default.attrs, self.color_cache.tag_default.attrs,
((x + 1, idx), (min_width.3, idx)), self.data_columns.columns[3]
.area()
.nth_row(idx)
.skip_cols(x + 1),
None, None,
); );
self.data_columns.columns[3][(x, idx)].set_bg(color); self.data_columns.columns[3].grid_mut()[(x, idx)].set_bg(color);
if _x < min_width.3 { if _x < min_width.3 {
self.data_columns.columns[3][(_x, idx)] self.data_columns.columns[3].grid_mut()[(_x, idx)]
.set_bg(color) .set_bg(color)
.set_keep_bg(true); .set_keep_bg(true);
} }
for x in (x + 1).._x { for x in (x + 1).._x {
self.data_columns.columns[3][(x, idx)] self.data_columns.columns[3].grid_mut()[(x, idx)]
.set_keep_fg(true) .set_keep_fg(true)
.set_keep_bg(true) .set_keep_bg(true)
.set_keep_attrs(true); .set_keep_attrs(true);
} }
self.data_columns.columns[3][(x, idx)].set_keep_bg(true); self.data_columns.columns[3].grid_mut()[(x, idx)].set_keep_bg(true);
x = _x + 1; x = _x + 1;
} }
x x
}; };
for x in x..min_width.3 { for x in x..min_width.3 {
self.data_columns.columns[3][(x, idx)] self.data_columns.columns[3].grid_mut()[(x, idx)]
.set_ch(' ') .set_ch(' ')
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
.set_attrs(row_attr.attrs); .set_attrs(row_attr.attrs);
@ -1533,7 +1607,6 @@ impl Component for CompactListing {
if !self.unfocused() { if !self.unfocused() {
let mut area = area; let mut area = area;
if !self.filter_term.is_empty() { if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area;
let (x, y) = grid.write_string( let (x, y) = grid.write_string(
&format!( &format!(
"{} results for `{}` (Press ESC to exit)", "{} results for `{}` (Press ESC to exit)",
@ -1544,7 +1617,7 @@ impl Component for CompactListing {
self.color_cache.theme_default.bg, self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs, self.color_cache.theme_default.attrs,
area, area,
Some(get_x(upper_left)), None,
); );
let default_cell = { let default_cell = {
let mut ret = Cell::with_char(' '); let mut ret = Cell::with_char(' ');
@ -1553,19 +1626,16 @@ impl Component for CompactListing {
.set_attrs(self.color_cache.theme_default.attrs); .set_attrs(self.color_cache.theme_default.attrs);
ret ret
}; };
for row in grid.bounds_iter(((x, y), set_y(bottom_right, y))) { for row in grid.bounds_iter(area.nth_row(y).skip_cols(x)) {
for c in row { for c in row {
grid[c] = default_cell; grid[c] = default_cell;
} }
} }
context context.dirty_areas.push_back(area);
.dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 1)));
area = (set_y(upper_left, y + 1), bottom_right); area = area.skip_rows(y + 1);
} }
let (upper_left, bottom_right) = area; let rows = area.height();
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
if let Some(modifier) = self.modifier_command.take() { if let Some(modifier) = self.modifier_command.take() {
if let Some(mvm) = self.movement.as_ref() { if let Some(mvm) = self.movement.as_ref() {

View File

@ -460,8 +460,8 @@ impl ListingTrait for ConversationsListing {
{ {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
} }
let upper_left = upper_left!(area); let upper_left = area.upper_left();
let bottom_right = bottom_right!(area); let bottom_right = area.bottom_right();
if let Err(message) = self.error.as_ref() { if let Err(message) = self.error.as_ref() {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
@ -528,10 +528,7 @@ impl ListingTrait for ConversationsListing {
if *idx >= self.length { if *idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = ( let new_area = area.skip_rows(3 * (*idx % rows)).take_rows(2);
set_y(upper_left, get_y(upper_left) + 3 * (*idx % rows)),
set_y(bottom_right, get_y(upper_left) + 3 * (*idx % rows) + 2),
);
self.highlight_line(grid, new_area, *idx, context); self.highlight_line(grid, new_area, *idx, context);
context.dirty_areas.push_back(new_area); context.dirty_areas.push_back(new_area);
} }
@ -552,13 +549,7 @@ impl ListingTrait for ConversationsListing {
self.highlight_line( self.highlight_line(
grid, grid,
( area.skip_rows(3 * (self.cursor_pos.2 % rows)).take_rows(2),
pos_inc(upper_left, (0, 3 * (self.cursor_pos.2 % rows))),
set_y(
bottom_right,
get_y(upper_left) + 3 * (self.cursor_pos.2 % rows) + 2,
),
),
self.cursor_pos.2, self.cursor_pos.2,
context, context,
); );
@ -888,12 +879,11 @@ impl ConversationsListing {
let account = &context.accounts[&self.cursor_pos.0]; let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1); let threads = account.collection.get_threads(self.cursor_pos.1);
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
let (mut upper_left, bottom_right) = area;
for (idx, ((thread_hash, root_env_hash), strings)) in for (idx, ((thread_hash, root_env_hash), strings)) in
self.rows.entries.iter().enumerate().skip(top_idx) self.rows.entries.iter().enumerate().skip(top_idx)
{ {
if !context.accounts[&self.cursor_pos.0].contains_key(*root_env_hash) { if !context.accounts[&self.cursor_pos.0].contains_key(*root_env_hash) {
panic!(); continue;
} }
let thread = threads.thread_ref(*thread_hash); let thread = threads.thread_ref(*thread_hash);
@ -909,11 +899,11 @@ impl ConversationsListing {
row_attr.fg, row_attr.fg,
row_attr.bg, row_attr.bg,
row_attr.attrs, row_attr.attrs,
(upper_left, bottom_right), area.skip_rows(idx),
None, None,
); );
for x in x..(x + 3) { for c in grid.row_iter(area, x..(x + 3), idx) {
grid[set_x(upper_left, x)].set_bg(row_attr.bg); grid[c].set_bg(row_attr.bg);
} }
let subject_attr = row_attr!( let subject_attr = row_attr!(
subject, subject,
@ -928,10 +918,10 @@ impl ConversationsListing {
subject_attr.fg, subject_attr.fg,
subject_attr.bg, subject_attr.bg,
subject_attr.attrs, subject_attr.attrs,
(set_x(upper_left, x), bottom_right), area.skip(idx, x),
None, None,
); );
let mut subject_overflowed = subject_overflowed > get_y(upper_left); let mut subject_overflowed = subject_overflowed > 0;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
if subject_overflowed { if subject_overflowed {
break; break;
@ -942,10 +932,10 @@ impl ConversationsListing {
self.color_cache.tag_default.fg, self.color_cache.tag_default.fg,
color, color,
self.color_cache.tag_default.attrs, self.color_cache.tag_default.attrs,
(set_x(upper_left, x + 1), bottom_right), area.skip(idx, x + 1),
None, None,
); );
if _y > get_y(upper_left) { if _y > 0 {
subject_overflowed = true; subject_overflowed = true;
break; break;
} }
@ -1054,7 +1044,7 @@ impl Component for ConversationsListing {
area, area,
Some(get_x(upper_left)), Some(get_x(upper_left)),
); );
for c in grid.row_iter(x..(get_x(bottom_right) + 1), y) { for c in grid.row_iter(area, x..(get_x(bottom_right) + 1), y) {
grid[c] = Cell::default(); grid[c] = Cell::default();
} }
@ -1278,12 +1268,12 @@ impl Component for ConversationsListing {
} }
let entry_area = ( let entry_area = (
set_x(upper_left, get_x(upper_left) + width!(area) / 3 + 2), set_x(upper_left, get_x(upper_left) + area.width() / 3 + 2),
bottom_right, bottom_right,
); );
let gap_area = ( let gap_area = (
pos_dec(upper_left!(entry_area), (1, 0)), pos_dec(entry_area.upper_left(), (1, 0)),
bottom_right!(entry_area), entry_area.bottom_right(),
); );
grid.clear_area(gap_area, self.color_cache.theme_default); grid.clear_area(gap_area, self.color_cache.theme_default);
context.dirty_areas.push_back(gap_area); context.dirty_areas.push_back(gap_area);

View File

@ -150,36 +150,38 @@ impl Component for OfflineListing {
None, None,
); );
grid.write_string( let (_, mut y_offset) = grid.write_string(
&err.to_string(), &err.to_string(),
error_message.fg, error_message.fg,
error_message.bg, error_message.bg,
error_message.attrs, error_message.attrs,
(set_x(upper_left!(area), x + 1), bottom_right!(area)), area.skip_cols(x + 1),
Some(get_x(upper_left!(area))), Some(0),
); );
y_offset += 1;
if let Some(msg) = self.messages.last() { if let Some(msg) = self.messages.last() {
grid.write_string( grid.write_string(
msg, msg,
text_unfocused.fg, text_unfocused.fg,
text_unfocused.bg, text_unfocused.bg,
Attr::BOLD, Attr::BOLD,
(pos_inc((0, 1), upper_left!(area)), bottom_right!(area)), area.skip_rows(y_offset),
None, None,
); );
} }
y_offset += 1;
for (i, msg) in self.messages.iter().rev().skip(1).enumerate() { for (i, msg) in self.messages.iter().rev().skip(1).enumerate() {
grid.write_string( grid.write_string(
msg, msg,
text_unfocused.fg, text_unfocused.fg,
text_unfocused.bg, text_unfocused.bg,
text_unfocused.attrs, text_unfocused.attrs,
(pos_inc((0, 2 + i), upper_left!(area)), bottom_right!(area)), area.skip_rows(y_offset + i),
None, None,
); );
} }
} else { } else {
let (_, mut y) = grid.write_string( grid.write_string(
"loading...", "loading...",
conf::value(context, "highlight").fg, conf::value(context, "highlight").fg,
conf::value(context, "highlight").bg, conf::value(context, "highlight").bg,
@ -192,16 +194,15 @@ impl Component for OfflineListing {
.iter() .iter()
.collect(); .collect();
jobs.sort_by_key(|(j, _)| *j); jobs.sort_by_key(|(j, _)| *j);
for (job_id, j) in jobs { for (i, (job_id, j)) in jobs.into_iter().enumerate() {
grid.write_string( grid.write_string(
&format!("{}: {:?}", job_id, j), &format!("{}: {:?}", job_id, j),
text_unfocused.fg, text_unfocused.fg,
text_unfocused.bg, text_unfocused.bg,
text_unfocused.attrs, text_unfocused.attrs,
(set_y(upper_left!(area), y + 1), bottom_right!(area)), area.skip_rows(i + 1),
None, None,
); );
y += 1;
} }
context context

View File

@ -26,22 +26,6 @@ use melib::{Address, SortField, SortOrder, ThreadNode};
use super::{EntryStrings, *}; use super::{EntryStrings, *};
use crate::{components::PageMovement, jobs::JoinHandle}; use crate::{components::PageMovement, jobs::JoinHandle};
macro_rules! address_list {
(($name:expr) as comma_sep_list) => {{
let mut ret: String =
$name
.into_iter()
.fold(String::new(), |mut s: String, n: &Address| {
s.extend(n.to_string().chars());
s.push_str(", ");
s
});
ret.pop();
ret.pop();
ret
}};
}
macro_rules! row_attr { macro_rules! row_attr {
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{ ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
let color_cache = &$color_cache; let color_cache = &$color_cache;
@ -210,6 +194,7 @@ impl MailListingTrait for PlainListing {
/// account mailbox the user has chosen. /// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) { fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true); self.set_dirty(true);
self.force_draw = true;
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
if !(self.cursor_pos.0 == self.new_cursor_pos.0 if !(self.cursor_pos.0 == self.new_cursor_pos.0
&& self.cursor_pos.1 == self.new_cursor_pos.1) && self.cursor_pos.1 == self.new_cursor_pos.1)
@ -227,19 +212,20 @@ impl MailListingTrait for PlainListing {
match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) { match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) {
Ok(()) => {} Ok(()) => {}
Err(_) => { Err(_) => {
self.length = 0;
let message: String = let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status(); context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] = if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) {
CellBuffer::new_with_context(message.len(), 1, None, context); let area = self.data_columns.columns[0].area();
self.length = 0; self.data_columns.columns[0].grid_mut().write_string(
self.data_columns.columns[0].write_string( message.as_str(),
message.as_str(), self.color_cache.theme_default.fg,
self.color_cache.theme_default.fg, self.color_cache.theme_default.bg,
self.color_cache.theme_default.bg, self.color_cache.theme_default.attrs,
self.color_cache.theme_default.attrs, area,
((0, 0), (message.len().saturating_sub(1), 0)), None,
None, );
); }
return; return;
} }
} }
@ -338,6 +324,7 @@ impl ListingTrait for PlainListing {
self.filtered_order.clear(); self.filtered_order.clear();
self.filter_term.clear(); self.filter_term.clear();
self.rows.row_updates.clear(); self.rows.row_updates.clear();
self.data_columns.clear();
} }
fn next_entry(&mut self, context: &mut Context) { fn next_entry(&mut self, context: &mut Context) {
@ -390,17 +377,12 @@ impl ListingTrait for PlainListing {
self.rows.selection[&i] self.rows.selection[&i]
); );
let (upper_left, bottom_right) = area; let x = self.data_columns.widths[0]
let x = get_x(upper_left)
+ self.data_columns.widths[0]
+ self.data_columns.widths[1] + self.data_columns.widths[1]
+ self.data_columns.widths[2] + self.data_columns.widths[2]
+ 3 * 2; + 3 * 2;
for c in grid.row_iter( for c in grid.row_iter(area, 0..area.width(), 0) {
get_x(upper_left)..(get_x(bottom_right) + 1),
get_y(upper_left),
) {
grid[c] grid[c]
.set_fg(row_attr.fg) .set_fg(row_attr.fg)
.set_bg(row_attr.bg) .set_bg(row_attr.bg)
@ -408,14 +390,11 @@ impl ListingTrait for PlainListing {
} }
grid.copy_area( grid.copy_area(
&self.data_columns.columns[3], self.data_columns.columns[3].grid(),
(set_x(upper_left, x), bottom_right), area.skip_cols(x),
( self.data_columns.columns[3].area().nth_row(idx),
(0, idx),
pos_dec(self.data_columns.columns[3].size(), (1, 1)),
),
); );
for c in grid.row_iter(x..get_x(bottom_right), get_y(upper_left)) { for c in grid.row_iter(area, x..area.width(), 0) {
grid[c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); grid[c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
} }
} }
@ -426,20 +405,18 @@ impl ListingTrait for PlainListing {
{ {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
} }
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if self.length == 0 { if self.length == 0 {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
grid.copy_area( grid.copy_area(
&self.data_columns.columns[0], self.data_columns.columns[0].grid(),
area, area,
((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))), self.data_columns.columns[0].area(),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
return; return;
} }
let rows = get_y(bottom_right) - get_y(upper_left) + 1; let rows = area.height();
if rows == 0 { if rows == 0 {
return; return;
} }
@ -478,6 +455,10 @@ impl ListingTrait for PlainListing {
} }
} }
if self.force_draw {
grid.clear_area(area, self.color_cache.theme_default);
}
let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); let prev_page_no = (self.cursor_pos.2).wrapping_div(rows);
let page_no = (self.new_cursor_pos.2).wrapping_div(rows); let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
@ -492,7 +473,7 @@ impl ListingTrait for PlainListing {
if idx >= self.length { if idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = nth_row_area(area, idx % rows); let new_area = area.nth_row(idx % rows);
self.data_columns self.data_columns
.draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area)); .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
if highlight { if highlight {
@ -514,18 +495,18 @@ impl ListingTrait for PlainListing {
self.cursor_pos.2 = self.new_cursor_pos.2; self.cursor_pos.2 = self.new_cursor_pos.2;
} }
if !self.force_draw {
grid.clear_area(area, self.color_cache.theme_default);
}
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
_ = self _ = self.data_columns.recalc_widths(area.size(), top_idx);
.data_columns
.recalc_widths((width!(area), height!(area)), top_idx);
grid.clear_area(area, self.color_cache.theme_default);
/* copy table columns */ /* copy table columns */
self.data_columns self.data_columns
.draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area)); .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
/* apply each row colors separately */ /* apply each row colors separately */
for i in top_idx..(top_idx + height!(area)) { for i in top_idx..(top_idx + area.height()) {
if let Some(row_attr) = self.rows.row_attr_cache.get(&i) { if let Some(row_attr) = self.rows.row_attr_cache.get(&i) {
grid.change_theme(nth_row_area(area, i % rows), *row_attr); grid.change_theme(area.nth_row(i % rows), *row_attr);
} }
} }
@ -537,18 +518,17 @@ impl ListingTrait for PlainListing {
true, true,
false false
); );
grid.change_theme(nth_row_area(area, self.cursor_pos.2 % rows), row_attr); grid.change_theme(area.nth_row(self.cursor_pos.2 % rows), row_attr);
/* clear gap if available height is more than count of entries */ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length { if top_idx + rows > self.length {
grid.clear_area( grid.change_theme(
( area.skip_rows(self.length - top_idx),
pos_inc(upper_left, (0, self.length - top_idx)),
bottom_right,
),
self.color_cache.theme_default, self.color_cache.theme_default,
); );
} }
self.force_draw = false;
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
@ -589,7 +569,7 @@ impl ListingTrait for PlainListing {
self.new_cursor_pos.2 = self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2); std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else { } else {
self.data_columns.columns[0] = CellBuffer::new_with_context(0, 0, None, context); _ = self.data_columns.columns[0].resize_with_context(0, 0, context);
} }
self.redraw_list( self.redraw_list(
context, context,
@ -867,7 +847,7 @@ impl PlainListing {
self.data_columns.elasticities[0].set_rigid(); self.data_columns.elasticities[0].set_rigid();
self.data_columns.elasticities[1].set_rigid(); self.data_columns.elasticities[1].set_rigid();
self.data_columns.elasticities[2].set_grow(5, Some(35)); self.data_columns.elasticities[2].set_grow(15, Some(35));
self.data_columns.elasticities[3].set_rigid(); self.data_columns.elasticities[3].set_rigid();
self.data_columns self.data_columns
.cursor_config .cursor_config
@ -881,17 +861,13 @@ impl PlainListing {
.set_even_odd_theme(self.color_cache.even, self.color_cache.odd); .set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
/* index column */ /* index column */
self.data_columns.columns[0] = _ = self.data_columns.columns[0].resize_with_context(min_width.0, self.rows.len(), context);
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
/* date column */ /* date column */
self.data_columns.columns[1] = _ = self.data_columns.columns[1].resize_with_context(min_width.1, self.rows.len(), context);
CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
/* from column */ /* from column */
self.data_columns.columns[2] = _ = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context);
CellBuffer::new_with_context(min_width.2, self.rows.len(), None, context);
/* subject column */ /* subject column */
self.data_columns.columns[3] = _ = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context);
CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
let iter = if self.filter_term.is_empty() { let iter = if self.filter_term.is_empty() {
Box::new(self.local_collection.iter().cloned()) Box::new(self.local_collection.iter().cloned())
@ -912,104 +888,157 @@ impl PlainListing {
//); //);
//log::debug!("{:#?}", context.accounts); //log::debug!("{:#?}", context.accounts);
panic!(); continue;
} }
let row_attr = self.rows.row_attr_cache[&idx]; let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = columns[0].write_string( let (x, _) = {
&idx.to_string(), let area = columns[0].area().nth_row(idx);
row_attr.fg, columns[0].grid_mut().write_string(
row_attr.bg, &idx.to_string(),
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.0, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[0].row_iter(x..min_width.0, idx) { None,
columns[0][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); )
}
let (x, _) = columns[1].write_string(
&strings.date,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((0, idx), (min_width.1, idx)),
None,
);
for c in columns[1].row_iter(x..min_width.1, idx) {
columns[1][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
}
let (x, _) = columns[2].write_string(
&strings.from,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((0, idx), (min_width.2, idx)),
None,
);
for c in columns[2].row_iter(x..min_width.2, idx) {
columns[2][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
}
let (x, _) = columns[3].write_string(
&strings.flag,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((0, idx), (min_width.3, idx)),
None,
);
let (x, _) = columns[3].write_string(
&strings.subject,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((x, idx), (min_width.3, idx)),
None,
);
let x = {
let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let color = color.unwrap_or(self.color_cache.tag_default.bg);
let (_x, _) = columns[3].write_string(
t,
self.color_cache.tag_default.fg,
color,
self.color_cache.tag_default.attrs,
((x + 1, idx), (min_width.3, idx)),
None,
);
for c in columns[3].row_iter(x..(x + 1), idx) {
columns[3][c].set_bg(color);
}
for c in columns[3].row_iter(_x..(_x + 1), idx) {
columns[3][c].set_bg(color).set_keep_bg(true);
}
for c in columns[3].row_iter((x + 1)..(_x + 1), idx) {
columns[3][c].set_keep_fg(true).set_keep_bg(true);
}
for c in columns[3].row_iter(x..(x + 1), idx) {
columns[3][c].set_keep_bg(true);
}
x = _x + 1;
}
x
}; };
for c in columns[3].row_iter(x..min_width.3, idx) { for c in {
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); let area = columns[0].area();
columns[0].grid_mut().row_iter(area, x..min_width.0, idx)
} {
columns[0].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
}
let (x, _) = {
let area = columns[1].area().nth_row(idx);
columns[1].grid_mut().write_string(
&strings.date,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
for c in {
let area = columns[1].area();
columns[1].grid_mut().row_iter(area, x..min_width.1, idx)
} {
columns[1].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
}
let (x, _) = {
let area = columns[2].area().nth_row(idx);
columns[2].grid_mut().write_string(
&strings.from,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
for c in {
let area = columns[2].area();
columns[2].grid_mut().row_iter(area, x..min_width.2, idx)
} {
columns[2].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs)
.set_ch(' ');
}
let (x, _) = {
let area = columns[3].area().nth_row(idx);
columns[3].grid_mut().write_string(
&strings.flag,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
let x = {
let area = columns[3].area().nth_row(idx).skip_cols(x);
columns[3]
.grid_mut()
.write_string(
&strings.subject,
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
.0
+ x
};
let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let color = color.unwrap_or(self.color_cache.tag_default.bg);
let _x = {
let area = columns[3].area().nth_row(idx).skip_cols(x + 1);
columns[3]
.grid_mut()
.write_string(
t,
self.color_cache.tag_default.fg,
color,
self.color_cache.tag_default.attrs,
area,
None,
)
.0
+ x
+ 1
};
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color);
}
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, _x..(_x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color).set_keep_bg(true);
}
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, (x + 1)..(_x + 1), idx)
} {
columns[3].grid_mut()[c]
.set_keep_fg(true)
.set_keep_bg(true)
.set_keep_attrs(true);
}
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_keep_bg(true);
}
x = _x + 2;
} }
} }
if self.length == 0 && self.filter_term.is_empty() { if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status(); let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] = if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) {
CellBuffer::new_with_context(message.len(), self.length + 1, None, context); let area = self.data_columns.columns[0].area();
self.data_columns.columns[0].write_string( self.data_columns.columns[0].grid_mut().write_string(
&message, message.as_str(),
self.color_cache.theme_default.fg, self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg, self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs, self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)), area,
None, None,
); );
}
} }
} }
@ -1044,97 +1073,152 @@ impl PlainListing {
let strings = self.make_entry_string(&envelope, context); let strings = self.make_entry_string(&envelope, context);
drop(envelope); drop(envelope);
let columns = &mut self.data_columns.columns; let columns = &mut self.data_columns.columns;
let min_width = ( {
columns[0].size().0, let area = columns[0].area().nth_row(idx);
columns[1].size().0, columns[0].grid_mut().clear_area(area, row_attr)
columns[2].size().0, };
columns[3].size().0, {
); let area = columns[1].area().nth_row(idx);
columns[1].grid_mut().clear_area(area, row_attr);
}
{
let area = columns[2].area().nth_row(idx);
columns[2].grid_mut().clear_area(area, row_attr);
}
{
let area = columns[3].area().nth_row(idx);
columns[3].grid_mut().clear_area(area, row_attr);
}
columns[0].clear_area(((0, idx), (min_width.0, idx)), row_attr); let (x, _) = {
columns[1].clear_area(((0, idx), (min_width.1, idx)), row_attr); let area = columns[0].area().nth_row(idx);
columns[2].clear_area(((0, idx), (min_width.2, idx)), row_attr); columns[0].grid_mut().write_string(
columns[3].clear_area(((0, idx), (min_width.3, idx)), row_attr); &idx.to_string(),
row_attr.fg,
let (x, _) = columns[0].write_string( row_attr.bg,
&idx.to_string(), row_attr.attrs,
row_attr.fg, area,
row_attr.bg, None,
row_attr.attrs, )
((0, idx), (min_width.0, idx)), };
None, for c in {
); let area = columns[0].area();
for c in columns[0].row_iter(x..min_width.0, idx) { columns[0].grid_mut().row_iter(area, x..area.width(), idx)
columns[0][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); } {
columns[0].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
} }
let (x, _) = columns[1].write_string( let (x, _) = {
&strings.date, let area = columns[1].area().nth_row(idx);
row_attr.fg, columns[1].grid_mut().write_string(
row_attr.bg, &strings.date,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.1, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[1].row_iter(x..min_width.1, idx) { None,
columns[1][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); )
};
for c in {
let area = columns[1].area();
columns[1].grid_mut().row_iter(area, x..area.width(), idx)
} {
columns[1].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
} }
let (x, _) = columns[2].write_string( let (x, _) = {
&strings.from, let area = columns[2].area().nth_row(idx);
row_attr.fg, columns[2].grid_mut().write_string(
row_attr.bg, &strings.from,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.2, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
for c in columns[2].row_iter(x..min_width.2, idx) { None,
columns[2][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); )
};
for c in {
let area = columns[2].area();
columns[2].grid_mut().row_iter(area, x..area.width(), idx)
} {
columns[2].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
} }
let (x, _) = columns[3].write_string( let (x, _) = {
&strings.flag, let area = columns[3].area().nth_row(idx);
row_attr.fg, columns[3].grid_mut().write_string(
row_attr.bg, &strings.flag,
row_attr.attrs, row_attr.fg,
((0, idx), (min_width.3, idx)), row_attr.bg,
None, row_attr.attrs,
); area,
let (x, _) = columns[3].write_string( None,
&strings.subject, )
row_attr.fg, };
row_attr.bg, let (x, _) = {
row_attr.attrs, let area = columns[3].area().nth_row(idx).skip_cols(x);
((x, idx), (min_width.3, idx)), columns[3].grid_mut().write_string(
None, &strings.subject,
); row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
let x = { let x = {
let mut x = x + 1; let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let color = color.unwrap_or(self.color_cache.tag_default.bg); let color = color.unwrap_or(self.color_cache.tag_default.bg);
let (_x, _) = columns[3].write_string( let (_x, _) = {
t, let area = columns[3].area().nth_row(idx).skip_cols(x + 1);
self.color_cache.tag_default.fg, columns[3].grid_mut().write_string(
color, t,
self.color_cache.tag_default.attrs, self.color_cache.tag_default.fg,
((x + 1, idx), (min_width.3, idx)), color,
None, self.color_cache.tag_default.attrs,
); area,
for c in columns[3].row_iter(x..(x + 1), idx) { None,
columns[3][c].set_bg(color); )
};
for c in {
let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color);
} }
for c in columns[3].row_iter(_x..(_x + 1), idx) { for c in {
columns[3][c].set_bg(color).set_keep_bg(true); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, _x..(_x + 1), idx)
} {
columns[3].grid_mut()[c].set_bg(color).set_keep_bg(true);
} }
for c in columns[3].row_iter((x + 1)..(_x + 1), idx) { for c in {
columns[3][c].set_keep_fg(true).set_keep_bg(true); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, (x + 1)..(_x + 1), idx)
} {
columns[3].grid_mut()[c].set_keep_fg(true).set_keep_bg(true);
} }
for c in columns[3].row_iter(x..(x + 1), idx) { for c in {
columns[3][c].set_keep_bg(true); let area = columns[3].area();
columns[3].grid_mut().row_iter(area, x..(x + 1), idx)
} {
columns[3].grid_mut()[c].set_keep_bg(true);
} }
x = _x + 1; x = _x + 2;
} }
x x
}; };
for c in columns[3].row_iter(x..min_width.3, idx) { for c in {
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs); let area = columns[3].area();
columns[3].grid().row_iter(area, x..area.width(), idx)
} {
columns[3].grid_mut()[c]
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
} }
*self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings); *self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings);
} }
@ -1154,7 +1238,6 @@ impl Component for PlainListing {
if matches!(self.focus, Focus::None) { if matches!(self.focus, Focus::None) {
let mut area = area; let mut area = area;
if !self.filter_term.is_empty() { if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area;
let (x, y) = grid.write_string( let (x, y) = grid.write_string(
&format!( &format!(
"{} results for `{}` (Press ESC to exit)", "{} results for `{}` (Press ESC to exit)",
@ -1165,22 +1248,16 @@ impl Component for PlainListing {
self.color_cache.theme_default.bg, self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs, self.color_cache.theme_default.attrs,
area, area,
Some(get_x(upper_left)), Some(0),
); );
grid.clear_area( grid.clear_area(area.skip(x, y).nth_row(y), self.color_cache.theme_default);
((x, y), set_y(bottom_right, y)), context.dirty_areas.push_back(area);
self.color_cache.theme_default,
);
context
.dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 1)));
area = (set_y(upper_left, y + 1), bottom_right); area = area.skip_rows(y + 1);
} }
let (upper_left, bottom_right) = area; let rows = area.height();
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
if let Some(modifier) = self.modifier_command.take() { if let Some(modifier) = self.modifier_command.take() {
if let Some(mvm) = self.movement.as_ref() { if let Some(mvm) = self.movement.as_ref() {
@ -1379,13 +1456,13 @@ impl Component for PlainListing {
self.draw_list(grid, area, context); self.draw_list(grid, area, context);
} }
} else { } else {
self.view_area = area.into();
if self.length == 0 && self.dirty { if self.length == 0 && self.dirty {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
self.dirty = false;
return; return;
} }
self.view_area = area.into();
} }
self.dirty = false; self.dirty = false;
} }
@ -1394,6 +1471,11 @@ impl Component for PlainListing {
let shortcuts = self.shortcuts(context); let shortcuts = self.shortcuts(context);
match (&event, self.focus) { match (&event, self.focus) {
(UIEvent::VisibilityChange(true), _) => {
self.force_draw = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref k), Focus::Entry) (UIEvent::Input(ref k), Focus::Entry)
if shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"]) => if shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"]) =>
{ {
@ -1493,7 +1575,7 @@ impl Component for PlainListing {
UIEvent::MailboxUpdate((ref idxa, ref idxf)) UIEvent::MailboxUpdate((ref idxa, ref idxf))
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) => if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) =>
{ {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, true);
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => { UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
@ -1617,10 +1699,11 @@ impl Component for PlainListing {
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
match self.focus { self.force_draw
Focus::None => self.dirty, || match self.focus {
Focus::Entry | Focus::EntryFullscreen => false, Focus::None => self.dirty,
} Focus::Entry | Focus::EntryFullscreen => false,
}
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {

View File

@ -489,8 +489,8 @@ impl ListingTrait for ThreadListing {
{ {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
} }
let upper_left = upper_left!(area); let upper_left = area.upper_left();
let bottom_right = bottom_right!(area); let bottom_right = area.bottom_right();
if self.length == 0 { if self.length == 0 {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -551,7 +551,7 @@ impl ListingTrait for ThreadListing {
if idx >= self.length { if idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = nth_row_area(area, idx % rows); let new_area = area.nth_row(idx % rows);
self.data_columns self.data_columns
.draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area)); .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
if let Some(env_hash) = self.get_env_under_cursor(idx) { if let Some(env_hash) = self.get_env_under_cursor(idx) {
@ -589,7 +589,7 @@ impl ListingTrait for ThreadListing {
_ = self _ = self
.data_columns .data_columns
.recalc_widths((width!(area), height!(area)), top_idx); .recalc_widths((area.width(), area.height()), top_idx);
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
/* copy table columns */ /* copy table columns */
self.data_columns self.data_columns
@ -598,7 +598,7 @@ impl ListingTrait for ThreadListing {
self.draw_relative_numbers(grid, area, top_idx); self.draw_relative_numbers(grid, area, top_idx);
} }
/* apply each row colors separately */ /* apply each row colors separately */
for idx in top_idx..(top_idx + height!(area)) { for idx in top_idx..(top_idx + area.height()) {
if let Some(env_hash) = self.get_env_under_cursor(idx) { if let Some(env_hash) = self.get_env_under_cursor(idx) {
let row_attr = row_attr!( let row_attr = row_attr!(
self.color_cache, self.color_cache,
@ -607,7 +607,7 @@ impl ListingTrait for ThreadListing {
self.cursor_pos.2 == idx, self.cursor_pos.2 == idx,
self.rows.selection[&env_hash] self.rows.selection[&env_hash]
); );
grid.change_theme(nth_row_area(area, idx % rows), row_attr); grid.change_theme(area.nth_row(idx % rows), row_attr);
} }
} }
@ -620,7 +620,7 @@ impl ListingTrait for ThreadListing {
true, // because self.cursor_pos.2 == idx, true, // because self.cursor_pos.2 == idx,
self.rows.selection[&env_hash] self.rows.selection[&env_hash]
); );
grid.change_theme(nth_row_area(area, self.cursor_pos.2 % rows), row_attr); grid.change_theme(area.nth_row(self.cursor_pos.2 % rows), row_attr);
} }
/* clear gap if available height is more than count of entries */ /* clear gap if available height is more than count of entries */
@ -1110,8 +1110,8 @@ impl ThreadListing {
fn draw_relative_numbers(&mut self, grid: &mut CellBuffer, area: Area, top_idx: usize) { fn draw_relative_numbers(&mut self, grid: &mut CellBuffer, area: Area, top_idx: usize) {
let width = self.data_columns.columns[0].size().0; let width = self.data_columns.columns[0].size().0;
let upper_left = upper_left!(area); let upper_left = area.upper_left();
for i in 0..height!(area) { for i in 0..area.height() {
let row_attr = if let Some(env_hash) = self.get_env_under_cursor(top_idx + i) { let row_attr = if let Some(env_hash) = self.get_env_under_cursor(top_idx + i) {
row_attr!( row_attr!(
self.color_cache, self.color_cache,
@ -1366,8 +1366,8 @@ impl Component for ThreadListing {
self.draw_list(grid, area, context); self.draw_list(grid, area, context);
} else { } else {
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
let upper_left = upper_left!(area); let upper_left = area.upper_left();
let bottom_right = bottom_right!(area); let bottom_right = area.bottom_right();
if self.length == 0 && self.dirty { if self.length == 0 && self.dirty {
grid.clear_area(area, self.color_cache.theme_default); grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);

View File

@ -22,12 +22,13 @@
use melib::{MailBackendExtensionStatus, SpecialUsageMailbox}; use melib::{MailBackendExtensionStatus, SpecialUsageMailbox};
use super::*; use super::*;
use crate::accounts::JobRequest;
#[derive(Debug)] #[derive(Debug)]
pub struct AccountStatus { pub struct AccountStatus {
cursor: (usize, usize), cursor: (usize, usize),
account_pos: usize, account_pos: usize,
content: CellBuffer, content: Screen<Virtual>,
dirty: bool, dirty: bool,
theme_default: ThemeAttribute, theme_default: ThemeAttribute,
id: ComponentId, id: ComponentId,
@ -48,8 +49,10 @@ impl AccountStatus {
.set_attrs(theme_default.attrs); .set_attrs(theme_default.attrs);
ret ret
}; };
let mut content = CellBuffer::new(120, 5, default_cell); let mut content = Screen::<Virtual>::new();
content.set_growable(true); content.grid_mut().default_cell = default_cell;
content.grid_mut().set_growable(true);
_ = content.resize(80, 20);
AccountStatus { AccountStatus {
cursor: (0, 0), cursor: (0, 0),
@ -60,54 +63,51 @@ impl AccountStatus {
id: ComponentId::default(), id: ComponentId::default(),
} }
} }
}
impl Component for AccountStatus { fn update_content(&mut self, (width, height): (usize, usize), context: &Context) {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.content.resize_with_context(width, height, context) {
if !self.dirty {
return; return;
} }
self.dirty = false;
let (mut width, _) = self.content.size();
let a = &context.accounts[self.account_pos]; let a = &context.accounts[self.account_pos];
let (_x, _y) = self.content.write_string( let area = self.content.area().skip_cols(1);
let (_x, _y) = self.content.grid_mut().write_string(
"Account ", "Account ",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs | Attr::UNDERLINE, self.theme_default.attrs | Attr::UNDERLINE,
((1, 0), (width - 1, 0)), area,
None, None,
); );
let (_x, _y) = self.content.write_string( let area = self.content.area().skip(_x + 1, _y);
let (_x, _y) = self.content.grid_mut().write_string(
a.name(), a.name(),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD | Attr::UNDERLINE, Attr::BOLD | Attr::UNDERLINE,
((_x, _y), (width - 1, _y)), area,
None, None,
); );
width = self.content.size().0;
let mut line = 2; let mut line = 2;
self.content.write_string( let area = self.content.area().skip(1, line);
self.content.grid_mut().write_string(
"In-progress jobs:", "In-progress jobs:",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
line += 2; line += 2;
for (job_id, req) in a.active_jobs.iter() { for (job_id, req) in a.active_jobs.iter() {
width = self.content.size().0; let area = self.content.area().skip(1, line);
use crate::accounts::JobRequest; let (x, y) = self.content.grid_mut().write_string(
let (x, y) = self.content.write_string(
&format!("{} {}", req, job_id), &format!("{} {}", req, job_id),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((1, line), (width - 1, line)), area,
None, None,
); );
if let JobRequest::DeleteMailbox { mailbox_hash, .. } if let JobRequest::DeleteMailbox { mailbox_hash, .. }
@ -116,12 +116,13 @@ impl Component for AccountStatus {
| JobRequest::Refresh { mailbox_hash, .. } | JobRequest::Refresh { mailbox_hash, .. }
| JobRequest::Fetch { mailbox_hash, .. } = req | JobRequest::Fetch { mailbox_hash, .. } = req
{ {
self.content.write_string( let area = self.content.area().skip(x + 1, y + line);
self.content.grid_mut().write_string(
a.mailbox_entries[mailbox_hash].name(), a.mailbox_entries[mailbox_hash].name(),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x + 1, y), (width - 1, y)), area,
None, None,
); );
} }
@ -130,18 +131,18 @@ impl Component for AccountStatus {
} }
line += 2; line += 2;
width = self.content.size().0;
let (_x, _y) = self.content.write_string( let area = self.content.area().skip(1, line);
let (_x, _y) = self.content.grid_mut().write_string(
"Tag support: ", "Tag support: ",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
width = self.content.size().0; let area = self.content.area().skip(_x + 1, _y + line);
self.content.write_string( self.content.grid_mut().write_string(
if a.backend_capabilities.supports_tags { if a.backend_capabilities.supports_tags {
"yes" "yes"
} else { } else {
@ -150,21 +151,21 @@ impl Component for AccountStatus {
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((_x, _y), (width - 1, line)), area,
None, None,
); );
width = self.content.size().0;
line += 1; line += 1;
let (_x, _y) = self.content.write_string( let area = self.content.area().skip(1, line);
let (_x, _y) = self.content.grid_mut().write_string(
"Search backend: ", "Search backend: ",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
width = self.content.size().0; let area = self.content.area().skip(_x + 1, _y + line);
self.content.write_string( self.content.grid_mut().write_string(
&match ( &match (
a.settings.conf.search_backend(), a.settings.conf.search_backend(),
a.backend_capabilities.supports_search, a.backend_capabilities.supports_search,
@ -187,18 +188,18 @@ impl Component for AccountStatus {
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((_x, _y), (width - 1, _y)), area,
None, None,
); );
width = self.content.size().0;
line += 1; line += 1;
self.content.write_string( let area = self.content.area().skip(1, line);
self.content.grid_mut().write_string(
"Special Mailboxes:", "Special Mailboxes:",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
for f in a for f in a
@ -207,38 +208,38 @@ impl Component for AccountStatus {
.map(|entry| &entry.ref_mailbox) .map(|entry| &entry.ref_mailbox)
.filter(|f| f.special_usage() != SpecialUsageMailbox::Normal) .filter(|f| f.special_usage() != SpecialUsageMailbox::Normal)
{ {
width = self.content.size().0;
line += 1; line += 1;
self.content.write_string( let area = self.content.area().skip(1, line);
self.content.grid_mut().write_string(
&format!("{}: {}", f.path(), f.special_usage()), &format!("{}: {}", f.path(), f.special_usage()),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((1, line), (width - 1, line)), area,
None, None,
); );
} }
line += 2; line += 2;
width = self.content.size().0; let area = self.content.area().skip(1, line);
self.content.write_string( self.content.grid_mut().write_string(
"Subscribed mailboxes:", "Subscribed mailboxes:",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
line += 2; line += 2;
for mailbox_node in a.list_mailboxes() { for mailbox_node in a.list_mailboxes() {
width = self.content.size().0;
let f: &Mailbox = &a[&mailbox_node.hash].ref_mailbox; let f: &Mailbox = &a[&mailbox_node.hash].ref_mailbox;
if f.is_subscribed() { if f.is_subscribed() {
self.content.write_string( let area = self.content.area().skip(1, line);
self.content.grid_mut().write_string(
f.path(), f.path(),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((1, line), (width - 1, line)), area,
None, None,
); );
line += 1; line += 1;
@ -246,14 +247,14 @@ impl Component for AccountStatus {
} }
line += 1; line += 1;
width = self.content.size().0;
if let Some(ref extensions) = a.backend_capabilities.extensions { if let Some(ref extensions) = a.backend_capabilities.extensions {
self.content.write_string( let area = self.content.area().skip(1, line);
self.content.grid_mut().write_string(
"Server Extensions:", "Server Extensions:",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
Attr::BOLD, Attr::BOLD,
((1, line), (width - 1, line)), area,
None, None,
); );
let max_name_width = std::cmp::max( let max_name_width = std::cmp::max(
@ -264,28 +265,27 @@ impl Component for AccountStatus {
.max() .max()
.unwrap_or(0), .unwrap_or(0),
); );
width = self.content.size().0; let area = self.content.area().skip(max_name_width + 6, line);
self.content.write_string( self.content.grid_mut().write_string(
"meli support:", "meli support:",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((max_name_width + 6, line), (width - 1, line)), area,
None, None,
); );
line += 1; line += 1;
for (name, status) in extensions.iter() { for (name, status) in extensions.iter() {
width = self.content.size().0; let area = self.content.area().skip(1, line);
self.content.write_string( self.content.grid_mut().write_string(
name.trim_at_boundary(30), name.trim_at_boundary(30),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((1, line), (width - 1, line)), area,
None, None,
); );
width = self.content.size().0;
let (x, y) = { let (x, y) = {
let (status, color) = match status { let (status, color) = match status {
MailBackendExtensionStatus::Unsupported { comment: _ } => { MailBackendExtensionStatus::Unsupported { comment: _ } => {
@ -298,12 +298,13 @@ impl Component for AccountStatus {
("enabled", Color::Green) ("enabled", Color::Green)
} }
}; };
self.content.write_string( let area = self.content.area().skip(max_name_width + 6, line);
self.content.grid_mut().write_string(
status, status,
color, color,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((max_name_width + 6, line), (width - 1, line)), area,
None, None,
) )
}; };
@ -312,28 +313,44 @@ impl Component for AccountStatus {
| MailBackendExtensionStatus::Supported { comment } | MailBackendExtensionStatus::Supported { comment }
| MailBackendExtensionStatus::Enabled { comment } => { | MailBackendExtensionStatus::Enabled { comment } => {
if let Some(s) = comment { if let Some(s) = comment {
let (x, y) = self.content.write_string( let area = self
.content
.area()
.skip(max_name_width + 6, line)
.skip(x, y);
let (_x, _y) = self.content.grid_mut().write_string(
" (", " (",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x, y), (width - 1, y)), area,
None, None,
); );
let (x, y) = self.content.write_string( let area = self
.content
.area()
.skip(max_name_width + 6, line)
.skip(x, y)
.skip(_x, _y);
let (__x, __y) = self.content.grid_mut().write_string(
s, s,
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x, y), (width - 1, y)), area,
None, None,
); );
self.content.write_string( let area = self
.content
.area()
.skip(max_name_width + 6, line)
.skip(x + _x + __x, y + _y + __y);
self.content.grid_mut().write_string(
")", ")",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x, y), (width - 1, y)), area,
None, None,
); );
} }
@ -342,11 +359,21 @@ impl Component for AccountStatus {
line += 1; line += 1;
} }
} }
}
}
impl Component for AccountStatus {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.dirty {
return;
}
self.dirty = false;
self.update_content(area.size(), context);
/* self.content may have been resized with write_string() calls above /* self.content may have been resized with write_string() calls above
* since it has growable set */ * since it has growable set */
let (width, height) = self.content.size(); let (width, height) = self.content.area().size();
let (cols, rows) = (width!(area), height!(area)); let (cols, rows) = area.size();
self.cursor = ( self.cursor = (
std::cmp::min(width.saturating_sub(cols), self.cursor.0), std::cmp::min(width.saturating_sub(cols), self.cursor.0),
std::cmp::min(height.saturating_sub(rows), self.cursor.1), std::cmp::min(height.saturating_sub(rows), self.cursor.1),
@ -354,18 +381,12 @@ impl Component for AccountStatus {
grid.clear_area(area, self.theme_default); grid.clear_area(area, self.theme_default);
grid.copy_area( grid.copy_area(
&self.content, self.content.grid(),
area, area,
( self.content
( .area()
std::cmp::min((width - 1).saturating_sub(cols), self.cursor.0), .skip(self.cursor.0, self.cursor.1)
std::cmp::min((height - 1).saturating_sub(rows), self.cursor.1), .take(cols, rows),
),
(
std::cmp::min(self.cursor.0 + cols, width - 1),
std::cmp::min(self.cursor.1 + rows, height - 1),
),
),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }

View File

@ -711,74 +711,61 @@ impl EnvelopeView {
impl Component for EnvelopeView { impl Component for EnvelopeView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
self.view_settings.theme_default = crate::conf::value(context, "theme_default"); self.view_settings.theme_default = crate::conf::value(context, "theme_default");
let headers = crate::conf::value(context, "mail.view.headers"); let hdr_theme = crate::conf::value(context, "mail.view.headers");
let headers_names = crate::conf::value(context, "mail.view.headers_names"); let hdr_name_theme = crate::conf::value(context, "mail.view.headers_names");
let headers_area = crate::conf::value(context, "mail.view.headers_area"); let hdr_area_theme = crate::conf::value(context, "mail.view.headers_area");
let y: usize = { let y: usize = {
if self.mode.is_source() { if self.mode.is_source() {
grid.clear_area(area, self.view_settings.theme_default); grid.clear_area(area, self.view_settings.theme_default);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
get_y(upper_left) 0
} else { } else {
let envelope = &self.mail; let envelope = &self.mail;
let height_p = self.pager.size().1; let height_p = self.pager.size().1;
let height = height!(area) let height = area
.height()
.saturating_sub(self.headers_no) .saturating_sub(self.headers_no)
.saturating_sub(1); .saturating_sub(1);
self.headers_no = 0; self.headers_no = 0;
let mut skip_header_ctr = self.headers_cursor; let mut skip_header_ctr = self.headers_cursor;
let sticky = self.view_settings.sticky_headers || height_p < height; let sticky = self.view_settings.sticky_headers || height_p < height;
let (_, mut y) = upper_left; let mut y = 0;
macro_rules! print_header { macro_rules! print_header {
($(($header:path, $string:expr)),*$(,)?) => { ($(($header:path, $string:expr)),*$(,)?) => {
$({ $({
if sticky || skip_header_ctr == 0 { if sticky || skip_header_ctr == 0 {
if y <= get_y(bottom_right) { if y <= area.height() {
grid.clear_area(
area.skip_rows(y),
hdr_area_theme,
);
let (_x, _y) = let (_x, _y) =
grid.write_string( grid.write_string(
&format!("{}:", $header), &format!("{}:", $header),
headers_names.fg, hdr_name_theme.fg,
headers_names.bg, hdr_name_theme.bg,
headers_names.attrs, hdr_name_theme.attrs,
(set_y(upper_left, y), bottom_right), area.skip_rows(y),
Some(get_x(upper_left)), Some(0),
); );
if let Some(cell) = grid.get_mut(_x, _y) { let (__x, __y) =
cell.set_ch(' ')
.set_fg(headers_area.fg)
.set_bg(headers_area.bg)
.set_attrs(headers_area.attrs);
}
let (_x, _y) =
grid.write_string( grid.write_string(
&$string, &$string,
headers.fg, hdr_theme.fg,
headers.bg, hdr_theme.bg,
headers.attrs, hdr_theme.attrs,
((_x + 1, _y), bottom_right), area.skip(_x+1, y+ _y),
Some(get_x(upper_left)), Some(0),
); );
y += _y +__y + 1;
grid.clear_area(
(
(std::cmp::min(_x, get_x(bottom_right)), _y),
(get_x(bottom_right), _y),
),
headers_area,
);
y = _y + 1;
} }
} else { } else {
skip_header_ctr -= 1; skip_header_ctr = skip_header_ctr.saturating_sub(1);
} }
self.headers_no += 1; self.headers_no += 1;
})+ })+
@ -871,34 +858,32 @@ impl Component for EnvelopeView {
ref unsubscribe, ref unsubscribe,
}) = list_management::ListActions::detect(envelope) }) = list_management::ListActions::detect(envelope)
{ {
let mut x = get_x(upper_left); let mut x = 0;
if let Some(id) = id { if let Some(id) = id {
if sticky || skip_header_ctr == 0 { if sticky || skip_header_ctr == 0 {
grid.clear_area( grid.clear_area(area.nth_row(y), hdr_area_theme);
(set_y(upper_left, y), set_y(bottom_right, y)),
headers_area,
);
let (_x, _) = grid.write_string( let (_x, _) = grid.write_string(
"List-ID: ", "List-ID: ",
headers_names.fg, hdr_name_theme.fg,
headers_names.bg, hdr_name_theme.bg,
headers_names.attrs, hdr_name_theme.attrs,
(set_y(upper_left, y), bottom_right), area.nth_row(y),
None,
);
let (_x, _y) = grid.write_string(
id,
headers.fg,
headers.bg,
headers.attrs,
((_x, y), bottom_right),
None, None,
); );
x = _x; x = _x;
if _y != y { let (_x, _y) = grid.write_string(
x = get_x(upper_left); id,
hdr_theme.fg,
hdr_theme.bg,
hdr_theme.attrs,
area.nth_row(y).skip_cols(_x),
None,
);
x += _x;
if _y != 0 {
x = 0;
} }
y = _y; y += _y;
} }
self.headers_no += 1; self.headers_no += 1;
} }
@ -906,71 +891,72 @@ impl Component for EnvelopeView {
if archive.is_some() || post.is_some() || unsubscribe.is_some() { if archive.is_some() || post.is_some() || unsubscribe.is_some() {
let (_x, _y) = grid.write_string( let (_x, _y) = grid.write_string(
" Available actions: [ ", " Available actions: [ ",
headers_names.fg, hdr_name_theme.fg,
headers_names.bg, hdr_name_theme.bg,
headers_names.attrs, hdr_name_theme.attrs,
((x, y), bottom_right), area.skip(x, y),
Some(get_x(upper_left)), Some(0),
); );
x = _x; x += _x;
y = _y; y += _y;
} }
if archive.is_some() { if archive.is_some() {
let (_x, _y) = grid.write_string( let (_x, _y) = grid.write_string(
"list-archive, ", "list-archive, ",
headers.fg, hdr_theme.fg,
headers.bg, hdr_theme.bg,
headers.attrs, hdr_theme.attrs,
((x, y), bottom_right), area.skip(x, y),
Some(get_x(upper_left)), Some(0),
); );
x = _x; x += _x;
y = _y; y += _y;
} }
if post.is_some() { if post.is_some() {
let (_x, _y) = grid.write_string( let (_x, _y) = grid.write_string(
"list-post, ", "list-post, ",
headers.fg, hdr_theme.fg,
headers.bg, hdr_theme.bg,
headers.attrs, hdr_theme.attrs,
((x, y), bottom_right), area.skip(x, y),
Some(get_x(upper_left)), Some(0),
); );
x = _x; x += _x;
y = _y; y += _y;
} }
if unsubscribe.is_some() { if unsubscribe.is_some() {
let (_x, _y) = grid.write_string( let (_x, _y) = grid.write_string(
"list-unsubscribe, ", "list-unsubscribe, ",
headers.fg, hdr_theme.fg,
headers.bg, hdr_theme.bg,
headers.attrs, hdr_theme.attrs,
((x, y), bottom_right), area.skip(x, y),
Some(get_x(upper_left)), Some(0),
); );
x = _x; x += _x;
y = _y; y += _y;
} }
if archive.is_some() || post.is_some() || unsubscribe.is_some() { if archive.is_some() || post.is_some() || unsubscribe.is_some() {
if x >= 2 { if x >= 2 {
if let Some(cell) = grid.get_mut(x - 2, y) { for c in grid.row_iter(area, (x - 2)..(x - 1), y) {
cell.set_ch(' '); grid[c].set_ch(' ');
} }
} }
if x > 0 { if x > 0 {
if let Some(cell) = grid.get_mut(x - 1, y) { for c in grid.row_iter(area, (x - 1)..x, y) {
cell.set_ch(']') grid[c]
.set_fg(headers_names.fg) .set_ch(']')
.set_bg(headers_names.bg) .set_fg(hdr_name_theme.fg)
.set_attrs(headers_names.attrs); .set_bg(hdr_name_theme.bg)
.set_attrs(hdr_name_theme.attrs);
} }
} }
} }
for x in x..=get_x(bottom_right) { for c in grid.row_iter(area, (x + 1)..area.width(), y) {
grid[(x, y)] grid[c]
.set_ch(' ') .set_ch(' ')
.set_fg(headers_area.fg) .set_fg(hdr_area_theme.fg)
.set_bg(headers_area.bg); .set_bg(hdr_area_theme.bg);
} }
y += 1; y += 1;
} }
@ -978,24 +964,21 @@ impl Component for EnvelopeView {
self.force_draw_headers = false; self.force_draw_headers = false;
grid.clear_area((set_y(upper_left, y), set_y(bottom_right, y)), headers_area); grid.clear_area(area.nth_row(y), hdr_area_theme);
context.dirty_areas.push_back(( context.dirty_areas.push_back(area.take_rows(y + 3));
upper_left,
set_y(bottom_right, std::cmp::min(y + 3, get_y(bottom_right))),
));
if !self.view_settings.sticky_headers { if !self.view_settings.sticky_headers {
let height_p = self.pager.size().1; let height_p = self.pager.size().1;
let height = height!(area).saturating_sub(y).saturating_sub(1); let height = area.height().saturating_sub(y).saturating_sub(1);
if self.pager.cursor_pos() >= self.headers_no { if self.pager.cursor_pos() >= self.headers_no {
get_y(upper_left) 0
} else if (height_p > height && self.headers_cursor < self.headers_no + 1) } else if (height_p > height && self.headers_cursor < self.headers_no + 1)
|| self.headers_cursor == 0 || self.headers_cursor == 0
|| height_p < height || height_p < height
{ {
y + 1 y + 1
} else { } else {
get_y(upper_left) 0
} }
} else { } else {
y + 1 y + 1
@ -1214,12 +1197,11 @@ impl Component for EnvelopeView {
if !s.is_dirty() { if !s.is_dirty() {
s.set_dirty(true); s.set_dirty(true);
} }
s.draw(grid, (set_y(upper_left, y), bottom_right), context); s.draw(grid, area.skip_rows(y), context);
} }
} }
_ => { _ => {
self.pager self.pager.draw(grid, area.skip_rows(y), context);
.draw(grid, (set_y(upper_left, y), bottom_right), context);
} }
} }
if let ForceCharset::Dialog(ref mut s) = self.force_charset { if let ForceCharset::Dialog(ref mut s) = self.force_charset {
@ -1228,21 +1210,17 @@ impl Component for EnvelopeView {
// Draw number command buffer at the bottom right corner: // Draw number command buffer at the bottom right corner:
let l = nth_row_area(area, height!(area)); let l = area.nth_row(area.height());
if self.cmd_buf.is_empty() { if self.cmd_buf.is_empty() {
grid.clear_area( grid.clear_area(l.skip_cols_from_end(8), self.view_settings.theme_default);
(pos_inc(l.0, (width!(area).saturating_sub(8), 0)), l.1),
self.view_settings.theme_default,
);
} else { } else {
let s = self.cmd_buf.to_string(); let s = self.cmd_buf.to_string();
grid.write_string( grid.write_string(
&s, &s,
self.view_settings.theme_default.fg, self.view_settings.theme_default.fg,
self.view_settings.theme_default.bg, self.view_settings.theme_default.bg,
self.view_settings.theme_default.attrs, self.view_settings.theme_default.attrs,
(pos_inc(l.0, (width!(area).saturating_sub(s.len()), 0)), l.1), l.skip_cols_from_end(8),
None, None,
); );
} }
@ -1284,8 +1262,35 @@ impl Component for EnvelopeView {
if sub.process_event(event, context) { if sub.process_event(event, context) {
return true; return true;
} }
} else if self.pager.process_event(event, context) { } else {
return true; if !self.view_settings.sticky_headers {
let shortcuts = self.pager.shortcuts(context);
match event {
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_up"])
&& self.pager.cursor_pos() == 0
&& self.headers_cursor > 0 =>
{
self.headers_cursor -= 1;
self.set_dirty(true);
return true;
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_down"])
&& self.headers_cursor < self.headers_no =>
{
self.headers_cursor += 1;
self.set_dirty(true);
return true;
}
_ => {}
}
}
if self.pager.process_event(event, context) {
return true;
}
} }
let shortcuts = &self.shortcuts(context); let shortcuts = &self.shortcuts(context);

View File

@ -65,7 +65,7 @@ pub struct ThreadView {
horizontal: Option<bool>, horizontal: Option<bool>,
movement: Option<PageMovement>, movement: Option<PageMovement>,
dirty: bool, dirty: bool,
content: CellBuffer, content: Screen<Virtual>,
id: ComponentId, id: ComponentId,
} }
@ -353,65 +353,99 @@ impl ThreadView {
e.heading = string; e.heading = string;
width = cmp::max(width, e.index.0 * 4 + e.heading.grapheme_width() + 2); width = cmp::max(width, e.index.0 * 4 + e.heading.grapheme_width() + 2);
} }
if !self.content.resize_with_context(width, height, context) {
return;
}
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
let highlight_theme = crate::conf::value(context, "highlight"); let highlight_theme = crate::conf::value(context, "highlight");
let mut content = CellBuffer::new_with_context(width, height, None, context);
if self.reversed { if self.reversed {
for (y, e) in self.entries.iter().rev().enumerate() { for (y, e) in self.entries.iter().rev().enumerate() {
/* Box character drawing stuff */ /* Box character drawing stuff */
if y > 0 && content.get_mut(e.index.0 * 4, 2 * y - 1).is_some() { if y > 0
&& self
.content
.grid_mut()
.get_mut(e.index.0 * 4, 2 * y - 1)
.is_some()
{
let index = (e.index.0 * 4, 2 * y - 1); let index = (e.index.0 * 4, 2 * y - 1);
if content[index].ch() == ' ' { if self.content.grid()[index].ch() == ' ' {
let mut ctr = 1; let mut ctr = 1;
while content.get(e.index.0 * 4 + ctr, 2 * y - 1).is_some() { while self
if content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' { .content
.grid()
.get(e.index.0 * 4 + ctr, 2 * y - 1)
.is_some()
{
if self.content.grid()[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' {
break; break;
} }
set_and_join_box( set_and_join_box(
&mut content, self.content.grid_mut(),
(e.index.0 * 4 + ctr, 2 * y - 1), (e.index.0 * 4 + ctr, 2 * y - 1),
BoxBoundary::Horizontal, BoxBoundary::Horizontal,
); );
ctr += 1; ctr += 1;
} }
set_and_join_box(&mut content, index, BoxBoundary::Horizontal); set_and_join_box(self.content.grid_mut(), index, BoxBoundary::Horizontal);
} }
} }
content.write_string( {
&e.heading, let area = self
if e.seen { .content
theme_default.fg .area()
} else { .skip(e.index.0 * 4 + 1, 2 * y)
highlight_theme.fg .take(e.heading.grapheme_width() + 1, height - 1);
}, self.content.grid_mut().write_string(
if e.seen { &e.heading,
theme_default.bg if e.seen {
} else { theme_default.fg
highlight_theme.bg } else {
}, highlight_theme.fg
theme_default.attrs, },
( if e.seen {
(e.index.0 * 4 + 1, 2 * y), theme_default.bg
(e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1), } else {
), highlight_theme.bg
None, },
); theme_default.attrs,
area,
None,
);
}
if let Some(len) = highlight_reply_subjects[y] { if let Some(len) = highlight_reply_subjects[y] {
let index = e.index.0 * 4 + 1 + e.heading.grapheme_width() - len; let index = e.index.0 * 4 + 1 + e.heading.grapheme_width() - len;
let area = ((index, 2 * y), (width - 2, 2 * y)); //let area = ((index, 2 * y), (width - 2, 2 * y));
content.change_theme(area, highlight_theme); let area = self.content.area().skip(index, 2 * y).take_rows(1);
self.content.grid_mut().change_theme(area, highlight_theme);
} }
set_and_join_box(&mut content, (e.index.0 * 4, 2 * y), BoxBoundary::Vertical);
set_and_join_box( set_and_join_box(
&mut content, self.content.grid_mut(),
(e.index.0 * 4, 2 * y),
BoxBoundary::Vertical,
);
set_and_join_box(
self.content.grid_mut(),
(e.index.0 * 4, 2 * y + 1), (e.index.0 * 4, 2 * y + 1),
BoxBoundary::Vertical, BoxBoundary::Vertical,
); );
for i in ((e.index.0 * 4) + 1)..width - 1 { for i in ((e.index.0 * 4) + 1)..width - 1 {
set_and_join_box(&mut content, (i, 2 * y + 1), BoxBoundary::Horizontal); set_and_join_box(
self.content.grid_mut(),
(i, 2 * y + 1),
BoxBoundary::Horizontal,
);
} }
set_and_join_box(&mut content, (width - 1, 2 * y), BoxBoundary::Vertical); set_and_join_box(
set_and_join_box(&mut content, (width - 1, 2 * y + 1), BoxBoundary::Vertical); self.content.grid_mut(),
(width - 1, 2 * y),
BoxBoundary::Vertical,
);
set_and_join_box(
self.content.grid_mut(),
(width - 1, 2 * y + 1),
BoxBoundary::Vertical,
);
} }
} else { } else {
for (y, e) in self.entries.iter().enumerate() { for (y, e) in self.entries.iter().enumerate() {
@ -420,71 +454,112 @@ impl ThreadView {
for i in 0..e.index.0 { for i in 0..e.index.0 {
let att = let att =
self.indentation_colors[(i).wrapping_rem(self.indentation_colors.len())]; self.indentation_colors[(i).wrapping_rem(self.indentation_colors.len())];
content.change_theme(((x, 2 * y), (x + 3, 2 * y + 1)), att); let area = self.content.area().skip(x, 2 * y).take(3, 1);
self.content.grid_mut().change_theme(area, att);
x += 4; x += 4;
} }
if y > 0 && content.get_mut(e.index.0 * 4, 2 * y - 1).is_some() { if y > 0
&& self
.content
.grid_mut()
.get_mut(e.index.0 * 4, 2 * y - 1)
.is_some()
{
let index = (e.index.0 * 4, 2 * y - 1); let index = (e.index.0 * 4, 2 * y - 1);
if content[index].ch() == ' ' { if self.content.grid()[index].ch() == ' ' {
let mut ctr = 1; let mut ctr = 1;
content[(e.index.0 * 4, 2 * y - 1)].set_bg(theme_default.bg); self.content.grid_mut()[(e.index.0 * 4, 2 * y - 1)]
while content.get(e.index.0 * 4 + ctr, 2 * y - 1).is_some() { .set_bg(theme_default.bg);
content[(e.index.0 * 4 + ctr, 2 * y - 1)].set_bg(theme_default.bg); while self
if content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' { .content
.grid()
.get(e.index.0 * 4 + ctr, 2 * y - 1)
.is_some()
{
self.content.grid_mut()[(e.index.0 * 4 + ctr, 2 * y - 1)]
.set_bg(theme_default.bg);
if self.content.grid()[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' {
break; break;
} }
set_and_join_box( set_and_join_box(
&mut content, self.content.grid_mut(),
(e.index.0 * 4 + ctr, 2 * y - 1), (e.index.0 * 4 + ctr, 2 * y - 1),
BoxBoundary::Horizontal, BoxBoundary::Horizontal,
); );
ctr += 1; ctr += 1;
} }
set_and_join_box(&mut content, index, BoxBoundary::Horizontal); set_and_join_box(self.content.grid_mut(), index, BoxBoundary::Horizontal);
} }
} }
content.write_string( {
&e.heading, let area = self
if e.seen { .content
theme_default.fg .area()
} else { .skip(e.index.0 * 4 + 1, 2 * y)
highlight_theme.fg .take(e.heading.grapheme_width(), height - 1);
}, self.content.grid_mut().write_string(
if e.seen { &e.heading,
theme_default.bg if e.seen {
} else { theme_default.fg
highlight_theme.bg } else {
}, highlight_theme.fg
theme_default.attrs, },
( if e.seen {
(e.index.0 * 4 + 1, 2 * y), theme_default.bg
(e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1), } else {
), highlight_theme.bg
None, },
); theme_default.attrs,
area,
None,
);
}
if highlight_reply_subjects[y].is_some() { if highlight_reply_subjects[y].is_some() {
let index = e.index.0 * 4 + 1; let index = e.index.0 * 4 + 1;
let area = ((index, 2 * y), (width - 2, 2 * y)); let area = self
content.change_theme(area, highlight_theme); .content
.area()
.skip(index, 2 * y)
.take(width - 2, 2 * y);
self.content.grid_mut().change_theme(area, highlight_theme);
} }
set_and_join_box(&mut content, (e.index.0 * 4, 2 * y), BoxBoundary::Vertical);
set_and_join_box( set_and_join_box(
&mut content, self.content.grid_mut(),
(e.index.0 * 4, 2 * y),
BoxBoundary::Vertical,
);
set_and_join_box(
self.content.grid_mut(),
(e.index.0 * 4, 2 * y + 1), (e.index.0 * 4, 2 * y + 1),
BoxBoundary::Vertical, BoxBoundary::Vertical,
); );
for i in ((e.index.0 * 4) + 1)..width - 1 { for i in ((e.index.0 * 4) + 1)..width - 1 {
set_and_join_box(&mut content, (i, 2 * y + 1), BoxBoundary::Horizontal); set_and_join_box(
self.content.grid_mut(),
(i, 2 * y + 1),
BoxBoundary::Horizontal,
);
} }
set_and_join_box(&mut content, (width - 1, 2 * y), BoxBoundary::Vertical); set_and_join_box(
set_and_join_box(&mut content, (width - 1, 2 * y + 1), BoxBoundary::Vertical); self.content.grid_mut(),
(width - 1, 2 * y),
BoxBoundary::Vertical,
);
set_and_join_box(
self.content.grid_mut(),
(width - 1, 2 * y + 1),
BoxBoundary::Vertical,
);
} }
for y in 0..height - 1 { for y in 0..height - 1 {
set_and_join_box(&mut content, (width - 1, y), BoxBoundary::Vertical); set_and_join_box(
self.content.grid_mut(),
(width - 1, y),
BoxBoundary::Vertical,
);
} }
} }
self.content = content;
self.visible_entries = vec![(0..self.entries.len()).collect()]; self.visible_entries = vec![(0..self.entries.len()).collect()];
} }
@ -517,17 +592,20 @@ impl ThreadView {
return; return;
} }
grid.copy_area(&self.content, dest_area, src_area); grid.copy_area(self.content.grid(), dest_area, src_area);
} }
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let (upper_left, bottom_right) = area; if self.entries.is_empty() {
let (width, height) = self.content.size(); context.dirty_areas.push_back(area);
return;
}
let height = self.content.area().height();
if height == 0 { if height == 0 {
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
return; return;
} }
let rows = (get_y(bottom_right) - get_y(upper_left)).wrapping_div(2); let rows = area.height();
if rows == 0 { if rows == 0 {
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
return; return;
@ -576,12 +654,9 @@ impl ThreadView {
let get_entry_area = |idx: usize, entries: &[ThreadEntry]| { let get_entry_area = |idx: usize, entries: &[ThreadEntry]| {
let entries = &entries; let entries = &entries;
let visual_indentation = entries[idx].index.0 * 4; let visual_indentation = entries[idx].index.0 * 4;
( self.content.area().skip(visual_indentation, 2 * idx).take(
(visual_indentation, 2 * idx), visual_indentation + entries[idx].heading.grapheme_width() + 1,
( 2 * idx,
visual_indentation + entries[idx].heading.grapheme_width() + 1,
2 * idx,
),
) )
}; };
@ -599,15 +674,9 @@ impl ThreadView {
let idx = *v; let idx = *v;
grid.copy_area( grid.copy_area(
&self.content, self.content.grid(),
( area.skip_rows(2 * visible_entry_counter),
pos_inc(upper_left, (0, 2 * visible_entry_counter)), // dest_area self.content.area().nth_row(2 * idx),
bottom_right,
),
(
(0, 2 * idx), //src_area
(width - 1, 2 * idx + 1),
),
); );
} }
/* If cursor position has changed, remove the highlight from the previous /* If cursor position has changed, remove the highlight from the previous
@ -617,36 +686,20 @@ impl ThreadView {
self.cursor_pos = visibles.len().saturating_sub(1); self.cursor_pos = visibles.len().saturating_sub(1);
} }
let idx = *visibles[self.cursor_pos]; let idx = *visibles[self.cursor_pos];
let src_area = { get_entry_area(idx, &self.entries) }; let src_area = get_entry_area(idx, &self.entries);
let visual_indentation = self.entries[idx].indentation * 4; let visual_indentation = self.entries[idx].indentation * 4;
let dest_area = ( let dest_area = area
pos_inc( .skip(visual_indentation, 2 * (self.cursor_pos - top_idx))
upper_left, .take(
(visual_indentation, 2 * (self.cursor_pos - top_idx)), visual_indentation + self.entries[idx].heading.grapheme_width() + 1,
), 2 * (self.cursor_pos - top_idx),
( );
cmp::min(
get_x(bottom_right),
get_x(upper_left)
+ visual_indentation
+ self.entries[idx].heading.grapheme_width()
+ 1,
),
cmp::min(
get_y(bottom_right),
get_y(upper_left) + 2 * (self.cursor_pos - top_idx),
),
),
);
self.highlight_line(grid, dest_area, src_area, idx, context); self.highlight_line(grid, dest_area, src_area, idx, context);
if rows < visibles.len() { if rows < visibles.len() {
ScrollBar::default().set_show_arrows(true).draw( ScrollBar::default().set_show_arrows(true).draw(
grid, grid,
( area.nth_col(area.width().saturating_sub(1)),
pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
bottom_right,
),
context, context,
2 * self.cursor_pos, 2 * self.cursor_pos,
rows, rows,
@ -655,14 +708,10 @@ impl ThreadView {
} }
if 2 * top_idx + rows > 2 * visibles.len() + 1 { if 2 * top_idx + rows > 2 * visibles.len() + 1 {
grid.clear_area( grid.clear_area(
( area.skip_rows(2 * (visibles.len() - top_idx) + 1),
pos_inc(upper_left, (0, 2 * (visibles.len() - top_idx) + 1)),
bottom_right,
),
crate::conf::value(context, "theme_default"), crate::conf::value(context, "theme_default"),
); );
} }
context.dirty_areas.push_back(area);
} else { } else {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -672,67 +721,36 @@ impl ThreadView {
self.visible_entries.iter().flat_map(|v| v.iter()).collect(); self.visible_entries.iter().flat_map(|v| v.iter()).collect();
for &idx in &[old_cursor_pos, self.cursor_pos] { for &idx in &[old_cursor_pos, self.cursor_pos] {
let entry_idx = *visibles[idx]; let entry_idx = *visibles[idx];
let src_area = { get_entry_area(entry_idx, &self.entries) }; let src_area = get_entry_area(entry_idx, &self.entries);
let visual_indentation = self.entries[entry_idx].indentation * 4; let visual_indentation = self.entries[entry_idx].indentation * 4;
let dest_area = ( let dest_area = area
pos_inc( .skip(visual_indentation, 2 * (visibles[..idx].len() - top_idx))
upper_left, .take(
(visual_indentation, 2 * (visibles[..idx].len() - top_idx)), visual_indentation + self.entries[entry_idx].heading.grapheme_width() + 1,
), 2 * (visibles[..idx].len() - top_idx),
( );
cmp::min(
get_x(bottom_right),
get_x(upper_left)
+ visual_indentation
+ self.entries[entry_idx].heading.grapheme_width()
+ 1,
),
cmp::min(
get_y(bottom_right),
get_y(upper_left) + 2 * (visibles[..idx].len() - top_idx),
),
),
);
self.highlight_line(grid, dest_area, src_area, entry_idx, context); self.highlight_line(grid, dest_area, src_area, entry_idx, context);
if rows < visibles.len() { if rows < visibles.len() {
ScrollBar::default().set_show_arrows(true).draw( ScrollBar::default().set_show_arrows(true).draw(
grid, grid,
( area.nth_col(area.width().saturating_sub(1)),
pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
bottom_right,
),
context, context,
2 * self.cursor_pos, 2 * self.cursor_pos,
rows, rows,
2 * visibles.len() + 1, 2 * visibles.len() + 1,
); );
context.dirty_areas.push_back((
upper_left!(area),
set_x(
bottom_right,
cmp::min(get_x(bottom_right!(area)), get_x(upper_left!(area)) + 1),
),
));
} }
let (upper_left, bottom_right) = dest_area;
context.dirty_areas.push_back((
upper_left,
(
get_x(bottom_right),
cmp::min(get_y(bottom_right), get_y(upper_left) + 1),
),
));
} }
} }
context.dirty_areas.push_back(area);
} }
fn draw_vert(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_vert(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area); if self.entries.is_empty() {
return;
let bottom_right = bottom_right!(area); }
let mid = get_x(upper_left) + self.content.size().0; let mid = area.width() / 2; //get_x(upper_left) + self.content.size().0;
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
/* First draw the thread subject on the first row */ /* First draw the thread subject on the first row */
@ -751,41 +769,26 @@ impl ThreadView {
}); });
let envelope: EnvelopeRef = account.collection.get_env(i); let envelope: EnvelopeRef = account.collection.get_env(i);
let (x, y) = grid.write_string( let (_, y) = grid.write_string(
&envelope.subject(), &envelope.subject(),
crate::conf::value(context, "highlight").fg, crate::conf::value(context, "highlight").fg,
theme_default.bg, theme_default.bg,
theme_default.attrs, theme_default.attrs,
area, area,
Some(get_x(upper_left)), Some(0),
); );
for x in x..=get_x(bottom_right) { context.dirty_areas.push_back(area);
grid[(x, y)]
.set_ch(' ')
.set_fg(theme_default.fg)
.set_bg(theme_default.bg);
}
context.dirty_areas.push_back((
upper_left,
set_y(bottom_right, std::cmp::min(get_y(bottom_right), y + 1)),
));
if y + 1 < get_y(bottom_right) && mid < get_x(bottom_right) {
context.dirty_areas.push_back((
(mid, y + 1),
set_x(bottom_right, std::cmp::min(get_x(bottom_right), mid)),
));
}
grid.clear_area(((mid, y + 1), set_x(bottom_right, mid)), theme_default); grid.clear_area(area.skip(mid, y + 1), theme_default);
y + 2 y + 2
} else { } else {
get_y(upper_left) + 2 2
}; };
let (width, height) = self.content.size(); let (width, height) = self.content.area().size();
if height == 0 || width == 0 { if height == 0 || width == 0 {
return; return;
} }
for x in get_x(upper_left)..=get_x(bottom_right) { for x in get_x(area.upper_left())..=get_x(area.bottom_right()) {
set_and_join_box(grid, (x, y - 1), BoxBoundary::Horizontal); set_and_join_box(grid, (x, y - 1), BoxBoundary::Horizontal);
grid[(x, y - 1)] grid[(x, y - 1)]
.set_fg(theme_default.fg) .set_fg(theme_default.fg)
@ -796,25 +799,18 @@ impl ThreadView {
ThreadViewFocus::None => { ThreadViewFocus::None => {
self.draw_list( self.draw_list(
grid, grid,
( area.skip_rows(y).take_cols(mid.saturating_sub(1)),
set_y(upper_left, std::cmp::min(get_y(bottom_right), y)),
set_x(bottom_right, std::cmp::min(get_x(bottom_right), mid - 1)),
),
context, context,
); );
let upper_left = (mid + 1, get_y(upper_left));
self.entries[self.new_expanded_pos].mailview.draw( self.entries[self.new_expanded_pos].mailview.draw(
grid, grid,
(upper_left, bottom_right), area.skip_cols(mid + 1),
context, context,
); );
} }
ThreadViewFocus::Thread => { ThreadViewFocus::Thread => {
grid.clear_area( grid.clear_area(area.skip(mid + 1, y - 1), theme_default);
((mid + 1, get_y(upper_left) + y - 1), bottom_right), self.draw_list(grid, area.skip_rows(y), context);
theme_default,
);
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
} }
ThreadViewFocus::MailView => { ThreadViewFocus::MailView => {
self.entries[self.new_expanded_pos] self.entries[self.new_expanded_pos]
@ -825,9 +821,12 @@ impl ThreadView {
} }
fn draw_horz(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_horz(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area); if self.entries.is_empty() {
let bottom_right = bottom_right!(area); return;
let total_rows = height!(area); }
let upper_left = area.upper_left();
let bottom_right = area.bottom_right();
let total_rows = area.height();
let pager_ratio = *mailbox_settings!( let pager_ratio = *mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1] context[self.coordinates.0][&self.coordinates.1]
@ -877,10 +876,7 @@ impl ThreadView {
.set_fg(theme_default.fg) .set_fg(theme_default.fg)
.set_bg(theme_default.bg); .set_bg(theme_default.bg);
} }
context.dirty_areas.push_back(( context.dirty_areas.push_back(area);
upper_left,
set_y(bottom_right, std::cmp::min(get_y(bottom_right), y + 2)),
));
y + 2 y + 2
}; };
@ -891,27 +887,17 @@ impl ThreadView {
.set_bg(theme_default.bg); .set_bg(theme_default.bg);
} }
let (width, height) = self.content.size(); let (width, height) = self.content.area().size();
if height == 0 || height == self.cursor_pos || width == 0 { if height == 0 || height == self.cursor_pos || width == 0 {
return; return;
} }
grid.clear_area( grid.clear_area(area.skip_rows(y).take_rows(mid + 1), theme_default);
(set_y(upper_left, y), set_y(bottom_right, mid + 1)),
theme_default,
);
let (width, height) = self.content.size();
match self.focus { match self.focus {
ThreadViewFocus::None => { ThreadViewFocus::None => {
let area = ( let area = area.skip_rows(y).take_rows(mid);
set_y(upper_left, y), let rows = area.height() / 2;
set_y(bottom_right, std::cmp::min(get_y(bottom_right), mid)),
);
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let rows = (get_y(bottom_right).saturating_sub(get_y(upper_left) + 1)) / 2;
if rows == 0 { if rows == 0 {
return; return;
} }
@ -919,20 +905,22 @@ impl ThreadView {
let top_idx = page_no * rows; let top_idx = page_no * rows;
grid.copy_area( grid.copy_area(
&self.content, self.content.grid(),
area, area,
((0, 2 * top_idx), (width - 1, height - 1)), self.content.area().skip_rows(2 * top_idx),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
ThreadViewFocus::Thread => { ThreadViewFocus::Thread => {
let area = ( let area = {
set_y(upper_left, std::cmp::min(y, get_y(bottom_right))), let val = area.skip_rows(y);
bottom_right, if val.height() < 20 {
); area
let upper_left = upper_left!(area); } else {
val
let rows = (get_y(bottom_right).saturating_sub(get_y(upper_left) + 1)) / 2; }
};
let rows = area.height() / 2;
if rows == 0 { if rows == 0 {
return; return;
} }
@ -940,9 +928,9 @@ impl ThreadView {
let top_idx = page_no * rows; let top_idx = page_no * rows;
grid.copy_area( grid.copy_area(
&self.content, self.content.grid(),
area, area,
((0, 2 * top_idx), (width - 1, height - 1)), self.content.area().skip_rows(2 * top_idx),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
@ -951,41 +939,35 @@ impl ThreadView {
match self.focus { match self.focus {
ThreadViewFocus::None => { ThreadViewFocus::None => {
let area = ( {
set_y(upper_left, std::cmp::min(get_y(bottom_right), mid)), let area = {
set_y(bottom_right, std::cmp::min(get_y(bottom_right), mid)), let val = area.skip_rows(mid);
); if val.height() < 20 {
context.dirty_areas.push_back(area); area
for x in get_x(upper_left)..=get_x(bottom_right) { } else {
set_and_join_box(grid, (x, mid), BoxBoundary::Horizontal); val
grid[(x, mid)] }
.set_fg(theme_default.fg) };
.set_bg(theme_default.bg); context.dirty_areas.push_back(area);
for x in get_x(area.upper_left())..=get_x(area.bottom_right()) {
set_and_join_box(grid, (x, mid), BoxBoundary::Horizontal);
grid[(x, mid)]
.set_fg(theme_default.fg)
.set_bg(theme_default.bg);
}
} }
let area = ( {
set_y(upper_left, std::cmp::min(get_y(bottom_right), y)), let area = area.skip_rows(y).take_rows(mid - 1);
set_y(bottom_right, std::cmp::min(get_y(bottom_right), mid - 1)), self.draw_list(grid, area, context);
); }
self.draw_list(grid, area, context); let area = area.take_rows(mid);
self.entries[self.new_expanded_pos].mailview.draw( self.entries[self.new_expanded_pos]
grid, .mailview
( .draw(grid, area, context);
set_y(upper_left, std::cmp::min(get_y(bottom_right), mid + 1)),
bottom_right,
),
context,
);
} }
ThreadViewFocus::Thread => { ThreadViewFocus::Thread => {
self.dirty = true; self.dirty = true;
self.draw_list( self.draw_list(grid, area.skip_rows(y), context);
grid,
(
set_y(upper_left, std::cmp::min(get_y(bottom_right), y)),
bottom_right,
),
context,
);
} }
ThreadViewFocus::MailView => { ThreadViewFocus::MailView => {
self.entries[self.new_expanded_pos] self.entries[self.new_expanded_pos]
@ -996,6 +978,9 @@ impl ThreadView {
} }
fn recalc_visible_entries(&mut self) { fn recalc_visible_entries(&mut self) {
if self.entries.is_empty() {
return;
}
if self if self
.entries .entries
.iter_mut() .iter_mut()
@ -1071,7 +1056,7 @@ impl std::fmt::Display for ThreadView {
impl Component for ThreadView { impl Component for ThreadView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let total_cols = width!(area); let total_cols = area.width();
if self.entries.is_empty() { if self.entries.is_empty() {
self.dirty = false; self.dirty = false;
return; return;
@ -1091,7 +1076,7 @@ impl Component for ThreadView {
.draw(grid, area, context); .draw(grid, area, context);
} else if self } else if self
.horizontal .horizontal
.unwrap_or(total_cols >= self.content.size().0 + 74) .unwrap_or(total_cols >= self.content.area().width() + 74)
{ {
self.draw_horz(grid, area, context); self.draw_horz(grid, area, context);
} else { } else {
@ -1101,7 +1086,7 @@ impl Component for ThreadView {
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
if let UIEvent::Action(Listing(OpenInNewTab)) = event { if let (UIEvent::Action(Listing(OpenInNewTab)), false) = (&event, self.entries.is_empty()) {
/* Handle this before self.mailview does */ /* Handle this before self.mailview does */
let mut new_tab = Self::new( let mut new_tab = Self::new(
self.coordinates, self.coordinates,
@ -1121,9 +1106,10 @@ impl Component for ThreadView {
if matches!( if matches!(
self.focus, self.focus,
ThreadViewFocus::None | ThreadViewFocus::MailView ThreadViewFocus::None | ThreadViewFocus::MailView
) && self.entries[self.new_expanded_pos] ) && !self.entries.is_empty()
.mailview && self.entries[self.new_expanded_pos]
.process_event(event, context) .mailview
.process_event(event, context)
{ {
return true; return true;
} }
@ -1220,15 +1206,20 @@ impl Component for ThreadView {
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["reverse_thread_order"]) => if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["reverse_thread_order"]) =>
{ {
self.reversed = !self.reversed; if !self.entries.is_empty() {
let expanded_hash = self.entries[self.expanded_pos].msg_hash; self.reversed = !self.reversed;
self.initiate(Some(expanded_hash), false, context); let expanded_hash = self.entries[self.expanded_pos].msg_hash;
self.set_dirty(true); self.initiate(Some(expanded_hash), false, context);
self.set_dirty(true);
}
true true
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["collapse_subtree"]) => if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["collapse_subtree"]) =>
{ {
if self.entries.is_empty() {
return true;
}
let current_pos = self.current_pos(); let current_pos = self.current_pos();
self.entries[current_pos].hidden = !self.entries[current_pos].hidden; self.entries[current_pos].hidden = !self.entries[current_pos].hidden;
self.entries[current_pos].dirty = true; self.entries[current_pos].dirty = true;
@ -1317,15 +1308,21 @@ impl Component for ThreadView {
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
self.dirty = value; self.dirty = value;
self.entries[self.new_expanded_pos] if !self.entries.is_empty() {
.mailview self.entries[self.new_expanded_pos]
.set_dirty(value); .mailview
.set_dirty(value);
}
} }
fn shortcuts(&self, context: &Context) -> ShortcutMaps { fn shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = self.entries[self.new_expanded_pos] let mut map = if !self.entries.is_empty() {
.mailview self.entries[self.new_expanded_pos]
.shortcuts(context); .mailview
.shortcuts(context)
} else {
ShortcutMaps::default()
};
map.insert( map.insert(
Shortcuts::GENERAL, Shortcuts::GENERAL,

View File

@ -273,7 +273,7 @@ impl MailboxManager {
if idx >= self.length { if idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = nth_row_area(area, idx % rows); let new_area = area.nth_row(idx % rows);
self.data_columns self.data_columns
.draw(grid, idx, self.cursor_pos, grid.bounds_iter(new_area)); .draw(grid, idx, self.cursor_pos, grid.bounds_iter(new_area));
let row_attr = if highlight { let row_attr = if highlight {
@ -295,18 +295,14 @@ impl MailboxManager {
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
_ = self _ = self
.data_columns .data_columns
.recalc_widths((width!(area), height!(area)), top_idx); .recalc_widths((area.width(), area.height()), top_idx);
grid.clear_area(area, self.theme_default); grid.clear_area(area, self.theme_default);
/* copy table columns */ /* copy table columns */
self.data_columns self.data_columns
.draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area)); .draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area));
/* highlight cursor */ /* highlight cursor */
grid.change_theme(area.nth_row(self.cursor_pos % rows), self.highlight_theme);
grid.change_theme(
nth_row_area(area, self.cursor_pos % rows),
self.highlight_theme,
);
/* clear gap if available height is more than count of entries */ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length { if top_idx + rows > self.length {

View File

@ -55,7 +55,7 @@ use smallvec::SmallVec;
use super::*; use super::*;
use crate::{ use crate::{
jobs::JobExecutor, jobs::JobExecutor,
terminal::{get_events, Screen}, terminal::{get_events, Screen, Tty},
}; };
struct InputHandler { struct InputHandler {
@ -276,7 +276,7 @@ impl Context {
/// `State` is responsible for managing the terminal and interfacing with /// `State` is responsible for managing the terminal and interfacing with
/// `melib` /// `melib`
pub struct State { pub struct State {
screen: Box<Screen>, screen: Box<Screen<Tty>>,
draw_rate_limit: RateLimit, draw_rate_limit: RateLimit,
child: Option<ForkType>, child: Option<ForkType>,
pub mode: UIMode, pub mode: UIMode,
@ -292,7 +292,7 @@ pub struct State {
display_messages_dirty: bool, display_messages_dirty: bool,
display_messages_initialised: bool, display_messages_initialised: bool,
display_messages_pos: usize, display_messages_pos: usize,
display_messages_area: Area, //display_messages_area: Area,
} }
#[derive(Debug)] #[derive(Debug)]
@ -413,20 +413,17 @@ impl State {
let working = Arc::new(()); let working = Arc::new(());
let control = Arc::downgrade(&working); let control = Arc::downgrade(&working);
let mut screen = Box::new(Screen::<Tty>::new().with_cols_and_rows(cols, rows));
screen
.tty_mut()
.set_mouse(settings.terminal.use_mouse.is_true())
.set_draw_fn(if settings.terminal.use_color() {
Screen::draw_horizontal_segment
} else {
Screen::draw_horizontal_segment_no_color
});
let mut s = State { let mut s = State {
screen: Box::new(Screen { screen,
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
mouse: settings.terminal.use_mouse.is_true(),
stdout: None,
draw_horizontal_segment_fn: if settings.terminal.use_color() {
Screen::draw_horizontal_segment
} else {
Screen::draw_horizontal_segment_no_color
},
}),
child: None, child: None,
mode: UIMode::Normal, mode: UIMode::Normal,
components: IndexMap::default(), components: IndexMap::default(),
@ -440,7 +437,6 @@ impl State {
display_messages_active: false, display_messages_active: false,
display_messages_dirty: false, display_messages_dirty: false,
display_messages_initialised: false, display_messages_initialised: false,
display_messages_area: ((0, 0), (0, 0)),
context: Box::new(Context { context: Box::new(Context {
accounts, accounts,
settings, settings,
@ -467,8 +463,8 @@ impl State {
}), }),
}; };
if s.context.settings.terminal.ascii_drawing { if s.context.settings.terminal.ascii_drawing {
s.screen.grid.set_ascii_drawing(true); s.screen.grid_mut().set_ascii_drawing(true);
s.screen.overlay_grid.set_ascii_drawing(true); s.screen.overlay_grid_mut().set_ascii_drawing(true);
} }
s.screen.switch_to_alternate_screen(&s.context); s.screen.switch_to_alternate_screen(&s.context);
@ -541,7 +537,6 @@ impl State {
self.rcv_event(UIEvent::Resize); self.rcv_event(UIEvent::Resize);
self.display_messages_dirty = true; self.display_messages_dirty = true;
self.display_messages_initialised = false; self.display_messages_initialised = false;
self.display_messages_area = ((0, 0), (0, 0));
// Invalidate dirty areas. // Invalidate dirty areas.
self.context.dirty_areas.clear(); self.context.dirty_areas.clear();
@ -569,259 +564,225 @@ impl State {
self.display_messages_dirty = true; self.display_messages_dirty = true;
self.display_messages_initialised = false; self.display_messages_initialised = false;
self.display_messages_expiration_start = None; self.display_messages_expiration_start = None;
areas.push(( areas.push(self.screen.area());
(0, 0),
(
self.screen.cols.saturating_sub(1),
self.screen.rows.saturating_sub(1),
),
));
} }
} }
/* Sort by x_start, ie upper_left corner's x coordinate */ /* Sort by x_start, ie upper_left corner's x coordinate */
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap()); areas.sort_by(|a, b| a.upper_left().0.partial_cmp(&b.upper_left().0).unwrap());
if self.display_messages_active { if self.display_messages_active {
/* Check if any dirty area intersects with the area occupied by floating /* Check if any dirty area intersects with the area occupied by
* notification box */ * floating notification box */
let (displ_top, displ_bot) = self.display_messages_area; //let (displ_top, displ_bot) = self.display_messages_area;
for &((top_x, top_y), (bottom_x, bottom_y)) in &areas { //for &((top_x, top_y), (bottom_x, bottom_y)) in &areas {
self.display_messages_dirty |= !(bottom_y < displ_top.1 // self.display_messages_dirty |= !(bottom_y < displ_top.1
|| displ_bot.1 < top_y // || displ_bot.1 < top_y
|| bottom_x < displ_top.0 // || bottom_x < displ_top.0
|| displ_bot.0 < top_x); // || displ_bot.0 < top_x);
} //}
} }
/* draw each dirty area */ /* draw each dirty area */
let rows = self.screen.rows; let rows = self.screen.area().height();
for y in 0..rows { for y in 0..rows {
let mut segment = None; let mut segment = None;
for ((x_start, y_start), (x_end, y_end)) in &areas { for ((x_start, y_start), (x_end, y_end)) in
if y < *y_start || y > *y_end { areas.iter().map(|a| (a.upper_left(), a.bottom_right()))
{
if y < y_start || y > y_end {
continue; continue;
} }
if let Some((x_start, x_end)) = segment.take() { if let Some((x_start, x_end)) = segment.take() {
(self.screen.draw_horizontal_segment_fn)( self.screen.draw(x_start, x_end, y);
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
x_start,
x_end,
y,
);
} }
match segment { match segment {
ref mut s @ None => { ref mut s @ None => {
*s = Some((*x_start, *x_end)); *s = Some((x_start, x_end));
} }
ref mut s @ Some(_) if s.unwrap().1 < *x_start => { ref mut s @ Some(_) if s.unwrap().1 < x_start => {
(self.screen.draw_horizontal_segment_fn)( self.screen.draw(s.unwrap().0, s.unwrap().1, y);
&mut self.screen.grid, *s = Some((x_start, x_end));
self.screen.stdout.as_mut().unwrap(),
s.unwrap().0,
s.unwrap().1,
y,
);
*s = Some((*x_start, *x_end));
} }
ref mut s @ Some(_) if s.unwrap().1 < *x_end => { ref mut s @ Some(_) if s.unwrap().1 < x_end => {
(self.screen.draw_horizontal_segment_fn)( self.screen.draw(s.unwrap().0, s.unwrap().1, y);
&mut self.screen.grid, *s = Some((s.unwrap().1, x_end));
self.screen.stdout.as_mut().unwrap(),
s.unwrap().0,
s.unwrap().1,
y,
);
*s = Some((s.unwrap().1, *x_end));
} }
Some((_, ref mut x)) => { Some((_, ref mut x)) => {
*x = *x_end; *x = x_end;
} }
} }
} }
if let Some((x_start, x_end)) = segment { if let Some((x_start, x_end)) = segment {
(self.screen.draw_horizontal_segment_fn)( self.screen.draw(x_start, x_end, y);
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
x_start,
x_end,
y,
);
} }
} }
if self.display_messages_dirty && self.display_messages_active { if self.display_messages_dirty && self.display_messages_active {
if let Some(DisplayMessage { //if let Some(DisplayMessage {
ref timestamp, // ref timestamp,
ref msg, // ref msg,
.. // ..
}) = self.display_messages.get(self.display_messages_pos) //}) = self.display_messages.get(self.display_messages_pos)
{ //{
if !self.display_messages_initialised { // if !self.display_messages_initialised {
{ // {
/* Clear area previously occupied by floating notification box */ // /* Clear area previously occupied by floating
let displ_area = self.display_messages_area; // * notification box */
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) { // //let displ_area = self.display_messages_area;
(self.screen.draw_horizontal_segment_fn)( // //for y in get_y(displ_area.upper_left())..
&mut self.screen.grid, // // =get_y(displ_area.bottom_right()) {
self.screen.stdout.as_mut().unwrap(), // // (self.screen.tty().draw_fn())(
get_x(upper_left!(displ_area)), // // self.screen.grid_mut(),
get_x(bottom_right!(displ_area)), // // self.screen.tty_mut().stdout_mut(),
y, // // get_x(displ_area.upper_left()),
); // // get_x(displ_area.bottom_right()),
} // // y,
} // // );
let noto_colors = crate::conf::value(&self.context, "status.notification"); // //}
use crate::melib::text_processing::{Reflow, TextProcessing}; // }
// let noto_colors = crate::conf::value(&self.context,
// "status.notification"); use
// crate::melib::text_processing::{Reflow, TextProcessing};
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.screen.cols / 3)); // let msg_lines =
let width = msg_lines // msg.split_lines_reflow(Reflow::All,
.iter() // Some(self.screen.area().width() / 3)); let width =
.map(|line| line.grapheme_len() + 4) // msg_lines .iter()
.max() // .map(|line| line.grapheme_len() + 4)
.unwrap_or(0); // .max()
// .unwrap_or(0);
let displ_area = place_in_area( // let displ_area = self.screen.area().place_inside(
( // (
(0, 0), // width,
( // std::cmp::min(self.screen.area().height(), msg_lines.len() +
self.screen.cols.saturating_sub(1), // 4), ),
self.screen.rows.saturating_sub(1), // false,
), // false,
), // );
(width, std::cmp::min(self.screen.rows, msg_lines.len() + 4)), // /*
false, // let box_displ_area = create_box(&mut self.screen.overlay_grid,
false, // displ_area); for row in
); // self.screen.overlay_grid.bounds_iter(box_displ_area) {
let box_displ_area = create_box(&mut self.screen.overlay_grid, displ_area); // for c in row {
for row in self.screen.overlay_grid.bounds_iter(box_displ_area) { // self.screen.overlay_grid[c]
for c in row { // .set_ch(' ')
self.screen.overlay_grid[c] // .set_fg(noto_colors.fg)
.set_ch(' ') // .set_bg(noto_colors.bg)
.set_fg(noto_colors.fg) // .set_attrs(noto_colors.attrs);
.set_bg(noto_colors.bg) // }
.set_attrs(noto_colors.attrs); // }
} // let ((x, mut y), box_displ_area_bottom_right) = box_displ_area;
} // for line in msg_lines
let ((x, mut y), box_displ_area_bottom_right) = box_displ_area; // .into_iter()
for line in msg_lines // .chain(Some(String::new()))
.into_iter() // .chain(Some(datetime::timestamp_to_string(*timestamp, None,
.chain(Some(String::new())) // false))) {
.chain(Some(datetime::timestamp_to_string(*timestamp, None, false))) // self.screen.overlay_grid.write_string(
{ // &line,
self.screen.overlay_grid.write_string( // noto_colors.fg,
&line, // noto_colors.bg,
noto_colors.fg, // noto_colors.attrs,
noto_colors.bg, // ((x, y), box_displ_area_bottom_right),
noto_colors.attrs, // Some(x),
((x, y), box_displ_area_bottom_right), // );
Some(x), // y += 1;
); // }
y += 1;
}
if self.display_messages.len() > 1 { // if self.display_messages.len() > 1 {
self.screen.overlay_grid.write_string( // self.screen.overlay_grid.write_string(
&if self.display_messages_pos == 0 { // &if self.display_messages_pos == 0 {
format!( // format!(
"Next: {}", // "Next: {}",
self.context.settings.shortcuts.general.info_message_next //
) // self.context.settings.shortcuts.general.info_message_next
} else if self.display_messages_pos + 1 == self.display_messages.len() { // )
format!( // } else if self.display_messages_pos + 1 ==
"Prev: {}", // self.display_messages.len() { format!(
self.context // "Prev: {}",
.settings // self.context
.shortcuts // .settings
.general // .shortcuts
.info_message_previous // .general
) // .info_message_previous
} else { // )
format!( // } else {
"Prev: {} Next: {}", // format!(
self.context // "Prev: {} Next: {}",
.settings // self.context
.shortcuts // .settings
.general // .shortcuts
.info_message_previous, // .general
self.context.settings.shortcuts.general.info_message_next // .info_message_previous,
) //
}, // self.context.settings.shortcuts.general.info_message_next
noto_colors.fg, // )
noto_colors.bg, // },
noto_colors.attrs, // noto_colors.fg,
((x, y), box_displ_area_bottom_right), // noto_colors.bg,
Some(x), // noto_colors.attrs,
); // ((x, y), box_displ_area_bottom_right),
} // Some(x),
self.display_messages_area = displ_area; // );
} // }
for y in get_y(upper_left!(self.display_messages_area)) // self.display_messages_area = displ_area;
..=get_y(bottom_right!(self.display_messages_area)) // */
{ // }
(self.screen.draw_horizontal_segment_fn)( // //for y in get_y(self.display_messages_area.upper_left())
&mut self.screen.overlay_grid, // // ..=get_y(self.display_messages_area.bottom_right())
self.screen.stdout.as_mut().unwrap(), // //{
get_x(upper_left!(self.display_messages_area)), // // (self.screen.tty().draw_fn())(
get_x(bottom_right!(self.display_messages_area)), // // &mut self.screen.overlay_grid,
y, // // self.screen.tty_mut().stdout_mut(),
); // // get_x(self.display_messages_area.upper_left()),
} // // get_x(self.display_messages_area.bottom_right()),
} // // y,
// // );
// //}
//}
self.display_messages_dirty = false; self.display_messages_dirty = false;
} else if self.display_messages_dirty { } else if self.display_messages_dirty {
/* Clear area previously occupied by floating notification box */ /* Clear area previously occupied by floating notification box */
let displ_area = self.display_messages_area; //let displ_area = self.display_messages_area;
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) { //for y in get_y(displ_area.upper_left())..=get_y(displ_area.bottom_right()) {
(self.screen.draw_horizontal_segment_fn)( // (self.screen.tty().draw_fn())(
&mut self.screen.grid, // self.screen.grid_mut(),
self.screen.stdout.as_mut().unwrap(), // self.screen.tty_mut().stdout_mut(),
get_x(upper_left!(displ_area)), // get_x(displ_area.upper_left()),
get_x(bottom_right!(displ_area)), // get_x(displ_area.bottom_right()),
y, // y,
); // );
} //}
self.display_messages_dirty = false; self.display_messages_dirty = false;
} }
if !self.overlay.is_empty() { if !self.overlay.is_empty() {
let area = center_area( let area: Area = self.screen.area();
( let overlay_area = area.center_inside((
(0, 0), if self.screen.cols() / 3 > 30 {
( self.screen.cols() / 3
self.screen.cols.saturating_sub(1), } else {
self.screen.rows.saturating_sub(1), self.screen.cols()
), },
), if self.screen.rows() / 5 > 10 {
( self.screen.rows() / 5
if self.screen.cols / 3 > 30 { } else {
self.screen.cols / 3 self.screen.rows()
} else { },
self.screen.cols ));
}, {
if self.screen.rows / 5 > 10 { let (grid, overlay_grid) = self.screen.grid_and_overlay_grid_mut();
self.screen.rows / 5 overlay_grid.copy_area(grid, area, area);
} else { self.overlay.get_index_mut(0).unwrap().1.draw(
self.screen.rows overlay_grid,
}, overlay_area,
), &mut self.context,
);
self.screen
.overlay_grid
.copy_area(&self.screen.grid, area, area);
self.overlay.get_index_mut(0).unwrap().1.draw(
&mut self.screen.overlay_grid,
area,
&mut self.context,
);
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.overlay_grid,
self.screen.stdout.as_mut().unwrap(),
get_x(upper_left!(area)),
get_x(bottom_right!(area)),
y,
); );
} }
for row in self.screen.overlay_grid().bounds_iter(overlay_area) {
self.screen
.draw_overlay(row.cols().start, row.cols().end, row.row_index());
}
} }
self.flush(); self.flush();
} }
@ -829,26 +790,17 @@ impl State {
/// Draw the entire screen from scratch. /// Draw the entire screen from scratch.
pub fn render(&mut self) { pub fn render(&mut self) {
self.screen.update_size(); self.screen.update_size();
let cols = self.screen.cols; self.context.dirty_areas.push_back(self.screen.area());
let rows = self.screen.rows;
self.context
.dirty_areas
.push_back(((0, 0), (cols - 1, rows - 1)));
self.redraw(); self.redraw();
} }
pub fn draw_component(&mut self, idx: usize) { pub fn draw_component(&mut self, idx: usize) {
let component = &mut self.components[idx]; let component = &mut self.components[idx];
let upper_left = (0, 0);
let bottom_right = (self.screen.cols - 1, self.screen.rows - 1);
if component.is_dirty() { if component.is_dirty() {
component.draw( let area = self.screen.area();
&mut self.screen.grid, component.draw(self.screen.grid_mut(), area, &mut self.context);
(upper_left, bottom_right),
&mut self.context,
);
} }
} }
@ -1024,11 +976,9 @@ impl State {
))); )));
} }
ToggleMouse => { ToggleMouse => {
self.screen.mouse = !self.screen.mouse; let new_val = !self.screen.tty().mouse();
self.screen.set_mouse(self.screen.mouse); self.screen.tty_mut().set_mouse(new_val);
self.rcv_event(UIEvent::StatusEvent(StatusEvent::SetMouse( self.rcv_event(UIEvent::StatusEvent(StatusEvent::SetMouse(new_val)));
self.screen.mouse,
)));
} }
Quit => { Quit => {
self.context self.context
@ -1061,7 +1011,7 @@ impl State {
Ok(action) => { Ok(action) => {
if action.needs_confirmation() { if action.needs_confirmation() {
let new = Box::new(UIConfirmationDialog::new( let new = Box::new(UIConfirmationDialog::new(
"You sure?", "Are you sure?",
vec![(true, "yes".to_string()), (false, "no".to_string())], vec![(true, "yes".to_string()), (false, "no".to_string())],
true, true,
Some(Box::new(move |id: ComponentId, result: bool| { Some(Box::new(move |id: ComponentId, result: bool| {

View File

@ -51,7 +51,7 @@ impl Default for SVGScreenshotFilter {
} }
impl Component for SVGScreenshotFilter { impl Component for SVGScreenshotFilter {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, context: &mut Context) { fn draw(&mut self, _grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.save_screenshot { if !self.save_screenshot {
return; return;
} }
@ -89,7 +89,7 @@ impl Component for SVGScreenshotFilter {
/* keep a map with used colors and write a stylesheet when we're done */ /* keep a map with used colors and write a stylesheet when we're done */
let mut classes: BTreeMap<(u8, u8, u8), usize> = BTreeMap::new(); let mut classes: BTreeMap<(u8, u8, u8), usize> = BTreeMap::new();
for (row_idx, row) in grid.bounds_iter(((0, 0), (width, height))).enumerate() { for (row_idx, row) in grid.bounds_iter(area).enumerate() {
text.clear(); text.clear();
escaped_text.clear(); escaped_text.clear();
/* Each row is a <g> group element, consisting of text elements */ /* Each row is a <g> group element, consisting of text elements */

View File

@ -39,7 +39,7 @@ pub mod text_editing;
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
pub use braille::BraillePixelIter; pub use braille::BraillePixelIter;
pub use screen::{Screen, StateStdout}; pub use screen::{Area, Screen, ScreenGeneration, StateStdout, Tty, Virtual};
pub use self::{cells::*, keys::*, position::*, text_editing::*}; pub use self::{cells::*, keys::*, position::*, text_editing::*};

File diff suppressed because it is too large Load Diff

View File

@ -42,8 +42,6 @@ use nix::{
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::terminal::position::*;
mod grid; mod grid;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
@ -54,7 +52,7 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
pub use grid::{EmbedGrid, EmbedTerminal}; pub use grid::{EmbedGrid, EmbedTerminal, ScreenBuffer};
// ioctl request code to "Make the given terminal the controlling terminal of the calling // ioctl request code to "Make the given terminal the controlling terminal of the calling
// process" // process"
use libc::TIOCSCTTY; use libc::TIOCSCTTY;
@ -193,7 +191,7 @@ fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc<Mutex<Emb
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum State { pub enum State {
ExpectingControlChar, ExpectingControlChar,
G0, // Designate G0 Character Set G0, // Designate G0 Character Set

View File

@ -26,11 +26,13 @@ use melib::{
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use super::*; use super::*;
use crate::terminal::{cells::*, Color}; use crate::terminal::{cells::*, Area, Color, Screen, Virtual};
#[derive(Debug)] #[derive(Debug, Default, Clone, Copy)]
enum ScreenBuffer { #[repr(u8)]
Normal, pub enum ScreenBuffer {
#[default]
Normal = 0,
Alternate, Alternate,
} }
@ -41,15 +43,15 @@ enum ScreenBuffer {
/// translated as changes to the grid, eg changes in a cell's colors. /// translated as changes to the grid, eg changes in a cell's colors.
/// ///
/// The main process copies the grid whenever the actual terminal is redrawn. /// The main process copies the grid whenever the actual terminal is redrawn.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct EmbedGrid { pub struct EmbedGrid {
cursor: (usize, usize), cursor: (usize, usize),
/// `[top;bottom]` /// `[top;bottom]`
scroll_region: ScrollRegion, scroll_region: ScrollRegion,
pub alternate_screen: CellBuffer, pub alternate_screen: Box<Screen<Virtual>>,
pub state: State, pub state: State,
/// (width, height) /// (width, height)
pub terminal_size: (usize, usize), terminal_size: (usize, usize),
initialized: bool, initialized: bool,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
@ -69,7 +71,7 @@ pub struct EmbedGrid {
wrap_next: bool, wrap_next: bool,
/// Store state in case a multi-byte character is encountered /// Store state in case a multi-byte character is encountered
codepoints: CodepointBuf, codepoints: CodepointBuf,
pub normal_screen: CellBuffer, pub normal_screen: Box<Screen<Virtual>>,
screen_buffer: ScreenBuffer, screen_buffer: ScreenBuffer,
} }
@ -177,7 +179,7 @@ impl EmbedTerminal {
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CodepointBuf { enum CodepointBuf {
None, None,
TwoCodepoints(u8), TwoCodepoints(u8),
@ -187,8 +189,7 @@ enum CodepointBuf {
impl EmbedGrid { impl EmbedGrid {
pub fn new() -> Self { pub fn new() -> Self {
let mut normal_screen = CellBuffer::default(); let normal_screen = Box::new(Screen::<Virtual>::new());
normal_screen.set_growable(true);
EmbedGrid { EmbedGrid {
cursor: (0, 0), cursor: (0, 0),
scroll_region: ScrollRegion { scroll_region: ScrollRegion {
@ -199,7 +200,7 @@ impl EmbedGrid {
}, },
terminal_size: (0, 0), terminal_size: (0, 0),
initialized: false, initialized: false,
alternate_screen: CellBuffer::default(), alternate_screen: Box::new(Screen::<Virtual>::new()),
state: State::Normal, state: State::Normal,
fg_color: Color::Default, fg_color: Color::Default,
bg_color: Color::Default, bg_color: Color::Default,
@ -220,15 +221,15 @@ impl EmbedGrid {
pub fn buffer(&self) -> &CellBuffer { pub fn buffer(&self) -> &CellBuffer {
match self.screen_buffer { match self.screen_buffer {
ScreenBuffer::Normal => &self.normal_screen, ScreenBuffer::Normal => self.normal_screen.grid(),
ScreenBuffer::Alternate => &self.alternate_screen, ScreenBuffer::Alternate => self.alternate_screen.grid(),
} }
} }
pub fn buffer_mut(&mut self) -> &mut CellBuffer { pub fn buffer_mut(&mut self) -> &mut CellBuffer {
match self.screen_buffer { match self.screen_buffer {
ScreenBuffer::Normal => &mut self.normal_screen, ScreenBuffer::Normal => self.normal_screen.grid_mut(),
ScreenBuffer::Alternate => &mut self.alternate_screen, ScreenBuffer::Alternate => self.alternate_screen.grid_mut(),
} }
} }
@ -236,30 +237,32 @@ impl EmbedGrid {
if new_val == self.terminal_size && self.initialized { if new_val == self.terminal_size && self.initialized {
return; return;
} }
if !self.alternate_screen.resize(new_val.0, new_val.1) {
return;
}
_ = self.normal_screen.resize(new_val.0, new_val.1);
self.initialized = true; self.initialized = true;
self.scroll_region.top = 0; self.scroll_region.top = 0;
self.scroll_region.bottom = new_val.1.saturating_sub(1); self.scroll_region.bottom = new_val.1.saturating_sub(1);
self.terminal_size = new_val; self.terminal_size = new_val;
if !self.alternate_screen.resize(new_val.0, new_val.1, None) {
panic!(
"Terminal size too big: ({} cols, {} rows)",
new_val.0, new_val.1
);
}
self.alternate_screen.clear(Some(Cell::default()));
if !self.normal_screen.resize(new_val.0, new_val.1, None) {
panic!(
"Terminal size too big: ({} cols, {} rows)",
new_val.0, new_val.1
);
}
self.normal_screen.clear(Some(Cell::default()));
self.cursor = (0, 0); self.cursor = (0, 0);
self.wrap_next = false; self.wrap_next = false;
} }
pub const fn terminal_size(&self) -> (usize, usize) {
self.terminal_size
}
pub const fn area(&self) -> Area {
match self.screen_buffer {
ScreenBuffer::Normal => self.normal_screen.area(),
_ => self.alternate_screen.area(),
}
}
pub fn process_byte(&mut self, stdin: &mut std::fs::File, byte: u8) { pub fn process_byte(&mut self, stdin: &mut std::fs::File, byte: u8) {
let area = self.area();
let EmbedGrid { let EmbedGrid {
ref mut cursor, ref mut cursor,
ref mut scroll_region, ref mut scroll_region,
@ -282,12 +285,12 @@ impl EmbedGrid {
ref mut normal_screen, ref mut normal_screen,
initialized: _, initialized: _,
} = self; } = self;
let mut grid = normal_screen; let mut grid = normal_screen.grid_mut();
let is_alternate = match *screen_buffer { let is_alternate = match *screen_buffer {
ScreenBuffer::Normal => false, ScreenBuffer::Normal => false,
_ => { _ => {
grid = alternate_screen; grid = alternate_screen.grid_mut();
true true
} }
}; };
@ -661,19 +664,10 @@ impl EmbedGrid {
/* Erase Below (default). */ /* Erase Below (default). */
grid.clear_area( grid.clear_area(
( area.skip_rows(std::cmp::min(
( cursor.1 + 1 + scroll_region.top,
0, terminal_size.1.saturating_sub(1),
std::cmp::min( )),
cursor.1 + 1 + scroll_region.top,
terminal_size.1.saturating_sub(1),
),
),
(
terminal_size.0.saturating_sub(1),
terminal_size.1.saturating_sub(1),
),
),
Default::default(), Default::default(),
); );
//log::trace!("{}", EscCode::from((&(*state), byte))); //log::trace!("{}", EscCode::from((&(*state), byte)));
@ -759,7 +753,7 @@ impl EmbedGrid {
//log::trace!("{}", EscCode::from((&(*state), byte))); //log::trace!("{}", EscCode::from((&(*state), byte)));
grid.clear_area( grid.clear_area(
((0, 0), pos_dec(*terminal_size, (1, 1))), area.take_cols(terminal_size.0).take_rows(terminal_size.1),
Default::default(), Default::default(),
); );
*state = State::Normal; *state = State::Normal;
@ -769,19 +763,10 @@ impl EmbedGrid {
/* Erase Below (default). */ /* Erase Below (default). */
grid.clear_area( grid.clear_area(
( area.skip_rows(std::cmp::min(
( cursor.1 + 1 + scroll_region.top,
0, terminal_size.1.saturating_sub(1),
std::cmp::min( )),
cursor.1 + 1 + scroll_region.top,
terminal_size.1.saturating_sub(1),
),
),
(
terminal_size.0.saturating_sub(1),
terminal_size.1.saturating_sub(1),
),
),
Default::default(), Default::default(),
); );
//log::trace!("{}", EscCode::from((&(*state), byte))); //log::trace!("{}", EscCode::from((&(*state), byte)));
@ -792,13 +777,7 @@ impl EmbedGrid {
/* Erase Above */ /* Erase Above */
grid.clear_area( grid.clear_area(
( area.take_rows(cursor.1.saturating_sub(1) + scroll_region.top),
(0, 0),
(
terminal_size.0.saturating_sub(1),
cursor.1.saturating_sub(1) + scroll_region.top,
),
),
Default::default(), Default::default(),
); );
//log::trace!("{}", EscCode::from((&(*state), byte))); //log::trace!("{}", EscCode::from((&(*state), byte)));
@ -808,10 +787,7 @@ impl EmbedGrid {
/* Erase in Display (ED), VT100. */ /* Erase in Display (ED), VT100. */
/* Erase All */ /* Erase All */
grid.clear_area( grid.clear_area(area, Default::default());
((0, 0), pos_dec(*terminal_size, (1, 1))),
Default::default(),
);
//log::trace!("{}", EscCode::from((&(*state), byte))); //log::trace!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal; *state = State::Normal;
} }

View File

@ -52,132 +52,6 @@ pub fn pos_dec(p: Pos, dec: (usize, usize)) -> Pos {
(p.0.saturating_sub(dec.0), p.1.saturating_sub(dec.1)) (p.0.saturating_sub(dec.0), p.1.saturating_sub(dec.1))
} }
/// An `Area` consists of two points: the upper left and bottom right corners.
///
/// Example:
/// ```no_run
/// let new_area = ((0, 0), (1, 1));
/// ```
pub type Area = (Pos, Pos);
#[inline(always)]
pub fn skip_rows(area: Area, n: usize) -> Option<Area> {
let (upper_left, bottom_right) = area;
if upper_left.1 + n <= bottom_right.1 {
return Some((pos_inc(upper_left, (0, n)), bottom_right));
}
None
}
/// Get an area's height
///
/// Example:
/// ```no_run
/// use meli::height;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(height!(new_area), 1);
/// ```
#[macro_export]
macro_rules! height {
($a:expr) => {
($crate::get_y($crate::bottom_right!($a)))
.saturating_sub($crate::get_y($crate::upper_left!($a)))
+ 1
};
}
/// Get an area's width
///
/// Example:
/// ```no_run
/// use meli::width;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(width!(new_area), 1);
/// ```
#[macro_export]
macro_rules! width {
($a:expr) => {
($crate::get_x($crate::bottom_right!($a)))
.saturating_sub($crate::get_x($crate::upper_left!($a)))
+ 1
};
}
/// Get the upper left Position of an area
///
/// Example:
/// ```no_run
/// use meli::upper_left;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(upper_left!(new_area), (0, 0));
/// ```
#[macro_export]
macro_rules! upper_left {
($a:expr) => {
$a.0
};
}
/// Get the bottom right Position of an area
///
/// Example:
/// ```no_run
/// use meli::bottom_right;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(bottom_right!(new_area), (1, 1));
/// ```
#[macro_export]
macro_rules! bottom_right {
($a:expr) => {
$a.1
};
}
/// Check if area is valid.
///
/// Example:
/// ```no_run
/// use meli::is_valid_area;
///
/// let valid_area = ((0, 0), (1, 1));
/// assert!(is_valid_area!(valid_area));
///
/// let invalid_area = ((2, 2), (1, 1));
/// assert!(!is_valid_area!(invalid_area));
/// ```
#[macro_export]
macro_rules! is_valid_area {
($a:expr) => {{
let upper_left = $crate::upper_left!($a);
let bottom_right = $crate::bottom_right!($a);
!($crate::get_y(upper_left) > $crate::get_y(bottom_right)
|| $crate::get_x(upper_left) > $crate::get_x(bottom_right))
}};
}
/// Place box given by `(width, height)` in center of `area`
pub fn center_area(area: Area, (width, height): (usize, usize)) -> Area {
let mid_x = { std::cmp::max(width!(area) / 2, width / 2) - width / 2 };
let mid_y = { std::cmp::max(height!(area) / 2, height / 2) - height / 2 };
let (upper_x, upper_y) = upper_left!(area);
let (max_x, max_y) = bottom_right!(area);
(
(
std::cmp::min(max_x, upper_x + mid_x),
std::cmp::min(max_y, upper_y + mid_y),
),
(
std::cmp::min(max_x, upper_x + mid_x + width),
std::cmp::min(max_y, upper_y + mid_y + height),
),
)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Alignment { pub enum Alignment {
/// Stretch to fill all space if possible, center if no meaningful way to /// Stretch to fill all space if possible, center if no meaningful way to
@ -191,78 +65,3 @@ pub enum Alignment {
#[default] #[default]
Center, Center,
} }
/// Place given area of dimensions `(width, height)` inside `area` according to
/// given alignment
pub fn align_area(
area: Area,
(width, height): (usize, usize),
vertical_alignment: Alignment,
horizontal_alignment: Alignment,
) -> Area {
let (top_x, width) = match horizontal_alignment {
Alignment::Center => (
{ std::cmp::max(width!(area) / 2, width / 2) - width / 2 },
width,
),
Alignment::Start => (0, width),
Alignment::End => (width!(area).saturating_sub(width), width!(area)),
Alignment::Fill => (0, width!(area)),
};
let (top_y, height) = match vertical_alignment {
Alignment::Center => (
{ std::cmp::max(height!(area) / 2, height / 2) - height / 2 },
height,
),
Alignment::Start => (0, height),
Alignment::End => (height!(area).saturating_sub(height), height!(area)),
Alignment::Fill => (0, height!(area)),
};
let (upper_x, upper_y) = upper_left!(area);
let (max_x, max_y) = bottom_right!(area);
(
(
std::cmp::min(max_x, upper_x + top_x),
std::cmp::min(max_y, upper_y + top_y),
),
(
std::cmp::min(max_x, upper_x + top_x + width),
std::cmp::min(max_y, upper_y + top_y + height),
),
)
}
/// Place box given by `(width, height)` in corner of `area`
pub fn place_in_area(area: Area, (width, height): (usize, usize), upper: bool, left: bool) -> Area {
let (upper_x, upper_y) = upper_left!(area);
let (max_x, max_y) = bottom_right!(area);
let x = if upper {
upper_x + 2
} else {
max_x.saturating_sub(2).saturating_sub(width)
};
let y = if left {
upper_y + 2
} else {
max_y.saturating_sub(2).saturating_sub(height)
};
(
(std::cmp::min(x, max_x), std::cmp::min(y, max_y)),
(
std::cmp::min(x + width, max_x),
std::cmp::min(y + height, max_y),
),
)
}
#[inline(always)]
/// Get `n`th row of `area` or its last one.
pub fn nth_row_area(area: Area, n: usize) -> Area {
let (upper_left, bottom_right) = area;
let (_, max_y) = bottom_right;
let y = std::cmp::min(max_y, get_y(upper_left) + n);
(set_y(upper_left, y), set_y(bottom_right, y))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,6 @@ pub struct Selector<
single_only: bool, single_only: bool,
entries: Vec<(T, bool)>, entries: Vec<(T, bool)>,
entry_titles: Vec<String>, entry_titles: Vec<String>,
pub content: CellBuffer,
theme_default: ThemeAttribute, theme_default: ThemeAttribute,
cursor: SelectorCursor, cursor: SelectorCursor,
@ -114,7 +113,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
return false; return false;
} }
let (width, height) = self.content.size();
let shortcuts = self.shortcuts(context); let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() { if !context.settings.terminal.use_color() {
@ -134,25 +132,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
/* User can select multiple entries, so Enter key toggles the entry under the /* User can select multiple entries, so Enter key toggles the entry under the
* cursor */ * cursor */
self.entries[c].1 = !self.entries[c].1; self.entries[c].1 = !self.entries[c].1;
if self.entries[c].1 {
self.content.write_string(
"x",
highlighted_attrs.fg,
highlighted_attrs.bg,
highlighted_attrs.attrs,
((1, c), (width - 1, c)),
None,
);
} else {
self.content.write_string(
" ",
highlighted_attrs.fg,
highlighted_attrs.bg,
highlighted_attrs.attrs,
((1, c), (width - 1, c)),
None,
);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -192,20 +171,7 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
if self.single_only { if self.single_only {
for c in self.content.row_iter(0..(width - 1), 0) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[0].1 = true; self.entries[0].1 = true;
} else {
for c in self.content.row_iter(0..3, 0) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(0); self.cursor = SelectorCursor::Entry(0);
self.dirty = true; self.dirty = true;
@ -216,34 +182,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
{ {
if self.single_only { if self.single_only {
// Redraw selection // Redraw selection
for c in self.content.row_iter(0..(width - 1), c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..(width - 1), c - 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false; self.entries[c].1 = false;
self.entries[c - 1].1 = true; self.entries[c - 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..3, c - 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(c - 1); self.cursor = SelectorCursor::Entry(c - 1);
self.dirty = true; self.dirty = true;
@ -253,23 +193,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
| (UIEvent::Input(ref key), SelectorCursor::Cancel) | (UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) =>
{ {
for c in self
.content
.row_iter(((width - OK_CANCEL.len()) / 2)..(width - 1), height - 1)
{
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
let c = self.entries.len().saturating_sub(1); let c = self.entries.len().saturating_sub(1);
self.cursor = SelectorCursor::Entry(c); self.cursor = SelectorCursor::Entry(c);
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -279,34 +204,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
{ {
if self.single_only { if self.single_only {
// Redraw selection // Redraw selection
for c in self.content.row_iter(0..(width - 1), c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..(width - 1), c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false; self.entries[c].1 = false;
self.entries[c + 1].1 = true; self.entries[c + 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..3, c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(c + 1); self.cursor = SelectorCursor::Entry(c + 1);
self.dirty = true; self.dirty = true;
@ -317,22 +216,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => && shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -340,26 +223,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{ {
self.cursor = SelectorCursor::Cancel; self.cursor = SelectorCursor::Cancel;
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET)
..((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET + CANCEL_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -367,27 +230,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.content.change_theme(
(
((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET, height - 1),
(
(width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET + CANCEL_LENGTH,
height - 1,
),
),
self.theme_default,
);
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -439,7 +281,6 @@ impl Component for UIConfirmationDialog {
return false; return false;
} }
let (width, height) = self.content.size();
let shortcuts = self.shortcuts(context); let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() { if !context.settings.terminal.use_color() {
@ -459,25 +300,6 @@ impl Component for UIConfirmationDialog {
/* User can select multiple entries, so Enter key toggles the entry under the /* User can select multiple entries, so Enter key toggles the entry under the
* cursor */ * cursor */
self.entries[c].1 = !self.entries[c].1; self.entries[c].1 = !self.entries[c].1;
if self.entries[c].1 {
self.content.write_string(
"x",
highlighted_attrs.fg,
highlighted_attrs.bg,
highlighted_attrs.attrs,
((1, c), (width - 1, c)),
None,
);
} else {
self.content.write_string(
" ",
highlighted_attrs.fg,
highlighted_attrs.bg,
highlighted_attrs.attrs,
((1, c), (width - 1, c)),
None,
);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -518,34 +340,8 @@ impl Component for UIConfirmationDialog {
{ {
if self.single_only { if self.single_only {
// Redraw selection // Redraw selection
for c in self.content.row_iter(0..(width - 1), c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..(width - 1), c - 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false; self.entries[c].1 = false;
self.entries[c - 1].1 = true; self.entries[c - 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..3, c - 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(c - 1); self.cursor = SelectorCursor::Entry(c - 1);
self.dirty = true; self.dirty = true;
@ -555,23 +351,8 @@ impl Component for UIConfirmationDialog {
| (UIEvent::Input(ref key), SelectorCursor::Cancel) | (UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) =>
{ {
for c in self
.content
.row_iter(((width - OK_CANCEL.len()) / 2)..(width - 1), height - 1)
{
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
let c = self.entries.len().saturating_sub(1); let c = self.entries.len().saturating_sub(1);
self.cursor = SelectorCursor::Entry(c); self.cursor = SelectorCursor::Entry(c);
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -579,20 +360,7 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
if self.single_only { if self.single_only {
for c in self.content.row_iter(0..(width - 1), 0) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[0].1 = true; self.entries[0].1 = true;
} else {
for c in self.content.row_iter(0..3, 0) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(0); self.cursor = SelectorCursor::Entry(0);
self.dirty = true; self.dirty = true;
@ -604,34 +372,8 @@ impl Component for UIConfirmationDialog {
{ {
if self.single_only { if self.single_only {
// Redraw selection // Redraw selection
for c in self.content.row_iter(0..(width - 1), c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..(width - 1), c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false; self.entries[c].1 = false;
self.entries[c + 1].1 = true; self.entries[c + 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(0..3, c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
} }
self.cursor = SelectorCursor::Entry(c + 1); self.cursor = SelectorCursor::Entry(c + 1);
self.dirty = true; self.dirty = true;
@ -642,22 +384,6 @@ impl Component for UIConfirmationDialog {
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => && shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(0..3, c) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -665,26 +391,6 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{ {
self.cursor = SelectorCursor::Cancel; self.cursor = SelectorCursor::Cancel;
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
}
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET)
..((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET + CANCEL_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -692,27 +398,6 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(
((width - OK_CANCEL.len()) / 2 + OK_OFFSET)
..((width - OK_CANCEL.len()) / 2 + OK_OFFSET + OK_LENGTH),
height - 1,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.content.change_theme(
(
((width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET, height - 1),
(
(width - OK_CANCEL.len()) / 2 + CANCEL_OFFSET + CANCEL_LENGTH,
height - 1,
),
),
self.theme_default,
);
self.dirty = true; self.dirty = true;
return true; return true;
} }
@ -761,7 +446,7 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
single_only: bool, single_only: bool,
done_fn: F, done_fn: F,
context: &Context, context: &Context,
) -> Selector<T, F> { ) -> Self {
let entry_titles = entries let entry_titles = entries
.iter_mut() .iter_mut()
.map(|(_id, ref mut title)| std::mem::take(title)) .map(|(_id, ref mut title)| std::mem::take(title))
@ -773,11 +458,10 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
identifiers[0].1 = true; identifiers[0].1 = true;
} }
let mut ret = Selector { let mut ret = Self {
single_only, single_only,
entries: identifiers, entries: identifiers,
entry_titles, entry_titles,
content: Default::default(),
cursor: SelectorCursor::Unfocused, cursor: SelectorCursor::Unfocused,
vertical_alignment: Alignment::Center, vertical_alignment: Alignment::Center,
horizontal_alignment: Alignment::Center, horizontal_alignment: Alignment::Center,
@ -794,60 +478,6 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
fn initialise(&mut self, context: &Context) { fn initialise(&mut self, context: &Context) {
self.theme_default = crate::conf::value(context, "theme_default"); self.theme_default = crate::conf::value(context, "theme_default");
let width = std::cmp::max(
OK_CANCEL.len(),
std::cmp::max(
self.entry_titles
.iter()
.max_by_key(|e| e.len())
.map(|v| v.len())
.unwrap_or(0),
self.title.len(),
),
) + 5;
let height = self.entries.len()
+ if self.single_only {
0
} else {
/* Extra room for buttons Okay/Cancel */
2
};
let mut content = CellBuffer::new_with_context(width, height, None, context);
if self.single_only {
for (i, e) in self.entry_titles.iter().enumerate() {
content.write_string(
e,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
((0, i), (width - 1, i)),
None,
);
}
} else {
for (i, e) in self.entry_titles.iter().enumerate() {
content.write_string(
&format!("[ ] {}", e),
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
((0, i), (width - 1, i)),
None,
);
}
content.write_string(
OK_CANCEL,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD,
(
((width - OK_CANCEL.len()) / 2, height - 1),
(width - 1, height - 1),
),
None,
);
}
self.content = content;
} }
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
@ -871,18 +501,17 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
Key::Char('\n') Key::Char('\n')
); );
let width = std::cmp::max( let width = std::cmp::max(
self.content.size().0 + 1, self.entry_titles.iter().map(|e| e.len()).max().unwrap_or(0) + 3,
std::cmp::max(self.title.len(), navigate_help_string.len()) + 3, std::cmp::max(self.title.len(), navigate_help_string.len()) + 3,
) + 3; ) + 3;
let height = self.content.size().1 + { let height = self.entries.len() + {
/* padding */ /* padding */
3 3
}; };
let dialog_area = align_area( let dialog_area = area.align_inside(
area,
(width, height), (width, height),
self.vertical_alignment,
self.horizontal_alignment, self.horizontal_alignment,
self.vertical_alignment,
); );
let inner_area = create_box(grid, dialog_area); let inner_area = create_box(grid, dialog_area);
grid.clear_area(inner_area, self.theme_default); grid.clear_area(inner_area, self.theme_default);
@ -892,10 +521,7 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD, self.theme_default.attrs | Attr::BOLD,
( dialog_area.skip_cols(2),
pos_inc(upper_left!(dialog_area), (2, 0)),
bottom_right!(dialog_area),
),
None, None,
); );
@ -904,19 +530,62 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs | Attr::ITALICS, self.theme_default.attrs | Attr::ITALICS,
( dialog_area.skip_cols(2).skip_rows(height),
pos_inc(upper_left!(dialog_area), (2, height)),
bottom_right!(dialog_area),
),
None, None,
); );
let inner_area = (
pos_inc(upper_left!(inner_area), (1, 1)),
bottom_right!(inner_area),
);
let (width, height) = self.content.size();
grid.copy_area(&self.content, inner_area, ((0, 0), (width - 1, height - 1))); let inner_area = inner_area.skip_cols(1).skip_rows(1);
let width = std::cmp::max(
OK_CANCEL.len(),
std::cmp::max(
self.entry_titles
.iter()
.max_by_key(|e| e.len())
.map(|v| v.len())
.unwrap_or(0),
self.title.len(),
),
) + 5;
let height = self.entries.len()
+ if self.single_only {
0
} else {
/* Extra room for buttons Okay/Cancel */
2
};
if self.single_only {
for (i, e) in self.entry_titles.iter().enumerate() {
grid.write_string(
e,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
inner_area.nth_row(i),
None,
);
}
} else {
for (i, e) in self.entry_titles.iter().enumerate() {
grid.write_string(
&format!("[{}] {}", if self.entries[i].1 { "x" } else { " " }, e),
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
inner_area.nth_row(i),
None,
);
}
grid.write_string(
OK_CANCEL,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD,
inner_area
.nth_row(height - 1)
.skip_cols((width - OK_CANCEL.len()) / 2),
None,
);
}
context.dirty_areas.push_back(dialog_area); context.dirty_areas.push_back(dialog_area);
self.dirty = false; self.dirty = false;
} }

View File

@ -62,11 +62,8 @@ impl HSplit {
impl Component for HSplit { impl Component for HSplit {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) { let upper_left = area.upper_left();
return; let bottom_right = area.bottom_right();
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_rows = get_y(bottom_right) - get_y(upper_left); let total_rows = get_y(bottom_right) - get_y(upper_left);
let bottom_component_height = (self.ratio * total_rows) / 100; let bottom_component_height = (self.ratio * total_rows) / 100;
let mid = get_y(upper_left) + total_rows - bottom_component_height; let mid = get_y(upper_left) + total_rows - bottom_component_height;
@ -157,11 +154,8 @@ impl VSplit {
impl Component for VSplit { impl Component for VSplit {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) { let upper_left = area.upper_left();
return; let bottom_right = area.bottom_right();
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_cols = get_x(bottom_right) - get_x(upper_left); let total_cols = get_x(bottom_right) - get_x(upper_left);
let visibility = (self.left.is_visible(), self.right.is_visible()); let visibility = (self.left.is_visible(), self.right.is_visible());
if visibility != self.prev_visibility { if visibility != self.prev_visibility {

View File

@ -22,6 +22,7 @@
use melib::text_processing::{LineBreakText, Truncate}; use melib::text_processing::{LineBreakText, Truncate};
use super::*; use super::*;
use crate::terminal::embed::EmbedGrid;
/// A pager for text. /// A pager for text.
/// `Pager` holds its own content in its own `CellBuffer` and when `draw` is /// `Pager` holds its own content in its own `CellBuffer` and when `draw` is
@ -49,7 +50,7 @@ pub struct Pager {
/// total height? Used to decide whether to accept `scroll_down` key /// total height? Used to decide whether to accept `scroll_down` key
/// events. /// events.
rows_lt_height: bool, rows_lt_height: bool,
filtered_content: Option<(String, Result<CellBuffer>)>, filtered_content: Option<(String, Result<EmbedGrid>)>,
text_lines: Vec<String>, text_lines: Vec<String>,
line_breaker: LineBreakText, line_breaker: LineBreakText,
movement: Option<PageMovement>, movement: Option<PageMovement>,
@ -180,7 +181,7 @@ impl Pager {
} }
pub fn filter(&mut self, cmd: &str) { pub fn filter(&mut self, cmd: &str) {
let _f = |bin: &str, text: &str| -> Result<CellBuffer> { let _f = |bin: &str, text: &str| -> Result<EmbedGrid> {
use std::{ use std::{
io::Write, io::Write,
process::{Command, Stdio}, process::{Command, Stdio},
@ -200,16 +201,16 @@ impl Pager {
.chain_err_summary(|| "Failed to wait on filter")? .chain_err_summary(|| "Failed to wait on filter")?
.stdout; .stdout;
let mut dev_null = std::fs::File::open("/dev/null")?; let mut dev_null = std::fs::File::open("/dev/null")?;
let mut embedded = crate::terminal::embed::EmbedGrid::new(); let mut embedded = EmbedGrid::new();
embedded.set_terminal_size((80, 20)); embedded.set_terminal_size((80, 20));
for b in out { for b in out {
embedded.process_byte(&mut dev_null, b); embedded.process_byte(&mut dev_null, b);
} }
Ok(std::mem::take(embedded.buffer_mut())) Ok(embedded)
}; };
let buf = _f(cmd, &self.text); let buf = _f(cmd, &self.text);
if let Some((width, height)) = buf.as_ref().ok().map(CellBuffer::size) { if let Some((width, height)) = buf.as_ref().ok().map(EmbedGrid::terminal_size) {
self.width = width; self.width = width;
self.height = height; self.height = height;
} }
@ -225,7 +226,7 @@ impl Pager {
} }
pub fn initialise(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { pub fn initialise(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let mut width = width!(area); let mut width = area.width();
if width < self.minimum_width { if width < self.minimum_width {
width = self.minimum_width; width = self.minimum_width;
} }
@ -253,7 +254,7 @@ impl Pager {
); );
} }
if let Some(pos) = search.positions.get(search.cursor) { if let Some(pos) = search.positions.get(search.cursor) {
if self.cursor.1 > pos.0 || self.cursor.1 + height!(area) < pos.0 { if self.cursor.1 > pos.0 || self.cursor.1 + area.height() < pos.0 {
self.cursor.1 = pos.0.saturating_sub(3); self.cursor.1 = pos.0.saturating_sub(3);
} }
} }
@ -262,7 +263,7 @@ impl Pager {
grid, grid,
area, area,
context, context,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * height!(area), self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * area.height(),
); );
} }
self.draw_page(grid, area, context); self.draw_page(grid, area, context);
@ -284,10 +285,10 @@ impl Pager {
if up_to == 0 { if up_to == 0 {
self.text_lines.extend(self.line_breaker.by_ref()); self.text_lines.extend(self.line_breaker.by_ref());
} else { } else {
if old_lines_no >= up_to + height!(area) { if old_lines_no >= up_to + area.height() {
return; return;
} }
let new_lines_no = (up_to + height!(area)) - old_lines_no; let new_lines_no = (up_to + area.height()) - old_lines_no;
self.text_lines self.text_lines
.extend(self.line_breaker.by_ref().take(new_lines_no)); .extend(self.line_breaker.by_ref().take(new_lines_no));
}; };
@ -310,21 +311,14 @@ impl Pager {
match filtered_content { match filtered_content {
Ok(ref content) => { Ok(ref content) => {
grid.copy_area( grid.copy_area(
content, content.buffer(),
area, area,
( content
( .area()
std::cmp::min( .skip_cols(self.cursor.0)
self.cursor.0, .skip_rows(self.cursor.1)
content.size().0.saturating_sub(width!(area)), .take_cols(content.terminal_size().0.saturating_sub(area.width()))
), .take_rows(content.terminal_size().1.saturating_sub(area.height())),
std::cmp::min(
self.cursor.1,
content.size().1.saturating_sub(height!(area)),
),
),
pos_dec(content.size(), (1, 1)),
),
); );
context context
.replies .replies
@ -346,86 +340,95 @@ impl Pager {
} }
} }
let (mut upper_left, bottom_right) = area;
for l in self
.text_lines
.iter()
.skip(self.cursor.1)
.take(height!(area))
{ {
grid.write_string( let mut area2 = area;
l, for l in self
self.colors.fg, .text_lines
self.colors.bg, .iter()
Attr::DEFAULT, .skip(self.cursor.1)
(upper_left, bottom_right), .take(area2.height())
None, {
); if area2.is_empty() {
if l.starts_with('⤷') { break;
grid[upper_left] }
.set_fg(crate::conf::value(context, "highlight").fg) grid.write_string(
.set_attrs(crate::conf::value(context, "highlight").attrs); l,
self.colors.fg,
self.colors.bg,
Attr::DEFAULT,
area2,
None,
);
if l.starts_with('⤷') {
grid[area2.upper_left()]
.set_fg(crate::conf::value(context, "highlight").fg)
.set_attrs(crate::conf::value(context, "highlight").attrs);
}
area2 = area2.skip_rows(1);
}
if area2.height() <= 1 {
grid.clear_area(area2, crate::conf::value(context, "theme_default"));
} }
upper_left = pos_inc(upper_left, (0, 1));
} }
if get_y(upper_left) <= get_y(bottom_right) {
grid.clear_area(
(upper_left, bottom_right),
crate::conf::value(context, "theme_default"),
);
}
let (upper_left, _bottom_right) = area;
#[cfg(feature = "regexp")]
{ {
for text_formatter in crate::conf::text_format_regexps(context, "pager.envelope.body") { #[cfg(feature = "regexp")]
let t = grid.insert_tag(text_formatter.tag); {
for (i, l) in self let area3 = area;
.text_lines for text_formatter in
.iter() crate::conf::text_format_regexps(context, "pager.envelope.body")
.skip(self.cursor.1)
.enumerate()
.take(height!(area) + 1)
{ {
let i = i + get_y(upper_left); let t = grid.insert_tag(text_formatter.tag);
for (start, end) in text_formatter.regexp.find_iter(l) { for (i, l) in self
let start = start + get_x(upper_left); .text_lines
let end = end + get_x(upper_left); .iter()
grid.set_tag(t, (start, i), (end, i)); .skip(self.cursor.1)
.enumerate()
.take(area3.height() + 1)
{
let i = i + area3.upper_left().1;
for (start, end) in text_formatter.regexp.find_iter(l) {
let start = start + area3.upper_left().0;
let end = end + area3.upper_left().0;
grid.set_tag(t, (start, i), (end, i));
}
} }
} }
} }
} if let Some(ref mut search) = self.search {
let cursor_line = self.cursor.1; // Last row will be reserved for the "Results for ..." line.
if let Some(ref mut search) = self.search { let area3 = area.skip_rows_from_end(1);
let results_attr = crate::conf::value(context, "pager.highlight_search"); let cursor_line = self.cursor.1;
let results_current_attr = let results_attr = crate::conf::value(context, "pager.highlight_search");
crate::conf::value(context, "pager.highlight_search_current"); let results_current_attr =
search.cursor = std::cmp::min(search.positions.len().saturating_sub(1), search.cursor); crate::conf::value(context, "pager.highlight_search_current");
for (i, (y, x)) in search search.cursor =
.positions std::cmp::min(search.positions.len().saturating_sub(1), search.cursor);
.iter() for (i, (y, offset)) in search
.enumerate() .positions
.filter(|(_, (y, _))| *y >= cursor_line) .iter()
.take(height!(area) + 1) .enumerate()
{ .filter(|(_, &(y, _))| y >= cursor_line && y < cursor_line + area3.height())
let x = *x + get_x(upper_left); {
let y = *y - cursor_line; let attr = if i == search.cursor {
for c in grid.row_iter( results_current_attr
x..x + search.pattern.grapheme_width(),
y + get_y(upper_left),
) {
if i == search.cursor {
grid[c]
.set_fg(results_current_attr.fg)
.set_bg(results_current_attr.bg)
.set_attrs(results_current_attr.attrs);
} else { } else {
results_attr
};
let (y, x) = (*y, *offset);
let row_iter = grid.row_iter(
area3.nth_row(y - cursor_line),
x..x + search.pattern.grapheme_width(),
0,
);
debug_assert_eq!(row_iter.area().width(), search.pattern.grapheme_width());
for c in row_iter {
grid[c] grid[c]
.set_fg(results_attr.fg) .set_fg(attr.fg)
.set_bg(results_attr.bg) .set_bg(attr.bg)
.set_attrs(results_attr.attrs); .set_attrs(attr.attrs);
} }
} }
} }
@ -435,9 +438,6 @@ impl Pager {
impl Component for Pager { impl Component for Pager {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) {
return;
}
if !self.is_dirty() { if !self.is_dirty() {
return; return;
} }
@ -448,14 +448,42 @@ impl Component for Pager {
self.dirty = false; self.dirty = false;
let height = height!(area); if self.height == 0 || self.width == 0 {
grid.clear_area(area, crate::conf::value(context, "theme_default"));
return;
}
let (mut cols, mut rows) = (area.width(), area.height());
let (has_more_lines, (width, height)) = if self.filtered_content.is_some() {
(false, (self.width, self.height))
} else {
(
!self.line_breaker.is_finished(),
(self.line_breaker.width().unwrap_or(cols), self.height),
)
};
if cols < 2 || rows < 2 {
return;
}
if self.show_scrollbar && rows < height {
cols -= 1;
rows -= 1;
} else if self.search.is_some() {
rows -= 1;
}
if self.show_scrollbar && cols < width {
rows -= 1;
}
if let Some(mvm) = self.movement.take() { if let Some(mvm) = self.movement.take() {
match mvm { match mvm {
PageMovement::Up(amount) => { PageMovement::Up(amount) => {
self.cursor.1 = self.cursor.1.saturating_sub(amount); self.cursor.1 = self.cursor.1.saturating_sub(amount);
} }
PageMovement::PageUp(multiplier) => { PageMovement::PageUp(multiplier) => {
self.cursor.1 = self.cursor.1.saturating_sub(height * multiplier); self.cursor.1 = self.cursor.1.saturating_sub(rows * multiplier);
} }
PageMovement::Down(amount) => { PageMovement::Down(amount) => {
if self.cursor.1 + amount + 1 < self.height { if self.cursor.1 + amount + 1 < self.height {
@ -467,22 +495,22 @@ impl Component for Pager {
grid, grid,
area, area,
context, context,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * height, self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * rows,
); );
} }
PageMovement::PageDown(multiplier) => { PageMovement::PageDown(multiplier) => {
if self.cursor.1 + height * multiplier + 1 < self.height { if self.cursor.1 + rows * multiplier + 1 < self.height {
self.cursor.1 += height * multiplier; self.cursor.1 += rows * multiplier;
} else if self.cursor.1 + height * multiplier > self.height { } else if self.cursor.1 + rows * multiplier > self.height {
self.cursor.1 = self.height.saturating_sub(1); self.cursor.1 = self.height.saturating_sub(1);
} else { } else {
self.cursor.1 = (self.height / height) * height; self.cursor.1 = (self.height / rows) * rows;
} }
self.draw_lines_up_to( self.draw_lines_up_to(
grid, grid,
area, area,
context, context,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * height, self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * rows,
); );
} }
PageMovement::Right(amount) => { PageMovement::Right(amount) => {
@ -505,9 +533,6 @@ impl Component for Pager {
} }
} }
if self.height == 0 || self.width == 0 {
return;
}
if let Some(ref mut search) = self.search { if let Some(ref mut search) = self.search {
if !search.positions.is_empty() { if !search.positions.is_empty() {
if let Some(mvm) = search.movement.take() { if let Some(mvm) = search.movement.take() {
@ -529,49 +554,19 @@ impl Component for Pager {
} }
grid.clear_area(area, crate::conf::value(context, "theme_default")); grid.clear_area(area, crate::conf::value(context, "theme_default"));
let (mut cols, mut rows) = (width!(area), height!(area));
let (has_more_lines, (width, height)) = if self.filtered_content.is_some() {
(false, (self.width, self.height))
} else {
(
!self.line_breaker.is_finished(),
(self.line_breaker.width().unwrap_or(cols), self.height),
)
};
self.cols_lt_width = cols + self.cursor.0 < width; self.cols_lt_width = cols + self.cursor.0 < width;
self.rows_lt_height = rows + self.cursor.1 < height; self.rows_lt_height = rows + self.cursor.1 < height;
if cols < 2 || rows < 2 {
return;
}
if self.show_scrollbar && rows < height {
cols -= 1;
rows -= 1;
} else if self.search.is_some() {
rows -= 1;
}
if self.show_scrollbar && cols < width {
rows -= 1;
}
self.cursor = ( self.cursor = (
std::cmp::min(width.saturating_sub(cols), self.cursor.0), std::cmp::min(width.saturating_sub(cols), self.cursor.0),
std::cmp::min(height.saturating_sub(rows), self.cursor.1), std::cmp::min(height.saturating_sub(rows), self.cursor.1),
); );
self.draw_page( self.draw_page(grid, area.take_cols(cols).take_rows(rows), context);
grid,
(upper_left!(area), pos_inc(upper_left!(area), (cols, rows))),
context,
);
if self.show_scrollbar && rows < height { if self.show_scrollbar && rows < height {
ScrollBar::default().set_show_arrows(true).draw( ScrollBar::default().set_show_arrows(true).draw(
grid, grid,
( area.nth_col(area.width()),
set_x(upper_left!(area), get_x(bottom_right!(area))),
bottom_right!(area),
),
context, context,
/* position */ /* position */
self.cursor.1, self.cursor.1,
@ -584,10 +579,7 @@ impl Component for Pager {
if self.show_scrollbar && cols < width { if self.show_scrollbar && cols < width {
ScrollBar::default().set_show_arrows(true).draw_horizontal( ScrollBar::default().set_show_arrows(true).draw_horizontal(
grid, grid,
( area.nth_row(area.height()),
set_y(upper_left!(area), get_y(bottom_right!(area))),
bottom_right!(area),
),
context, context,
self.cursor.0, self.cursor.0,
cols, cols,
@ -635,23 +627,26 @@ impl Component for Pager {
if !context.settings.terminal.use_color() { if !context.settings.terminal.use_color() {
attribute.attrs |= Attr::REVERSE; attribute.attrs |= Attr::REVERSE;
} }
let (_, y) = grid.write_string( grid.write_string(
&status_message, &status_message,
attribute.fg, attribute.fg,
attribute.bg, attribute.bg,
attribute.attrs, attribute.attrs,
( area.nth_row(area.height().saturating_sub(1)),
set_y(upper_left!(area), get_y(bottom_right!(area))),
bottom_right!(area),
),
None, None,
); );
/* set search pattern to italics */ /* set search pattern to italics */
let start_x = get_x(upper_left!(area)) + RESULTS_STR.len(); let start_x = RESULTS_STR.len();
for c in grid.row_iter(start_x..(start_x + search.pattern.grapheme_width()), y) { let row_iter = grid.row_iter(
area.nth_row(area.height().saturating_sub(1)),
start_x..(start_x + search.pattern.grapheme_width()),
0,
);
debug_assert_eq!(row_iter.area().width(), search.pattern.grapheme_width());
for c in row_iter {
grid[c].set_attrs(attribute.attrs | Attr::ITALICS); grid[c].set_attrs(attribute.attrs | Attr::ITALICS);
} }
}; }
} }
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }

View File

@ -23,7 +23,10 @@
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use super::*; use super::*;
use crate::segment_tree::SegmentTree; use crate::{
segment_tree::SegmentTree,
terminal::{Screen, Virtual},
};
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub enum ColumnElasticity { pub enum ColumnElasticity {
@ -122,7 +125,7 @@ impl TableCursorConfig {
pub struct DataColumns<const N: usize> { pub struct DataColumns<const N: usize> {
pub cursor_config: TableCursorConfig, pub cursor_config: TableCursorConfig,
pub theme_config: TableThemeConfig, pub theme_config: TableThemeConfig,
pub columns: Box<[CellBuffer; N]>, pub columns: Box<[Screen<Virtual>; N]>,
/// widths of columns calculated in first draw and after size changes /// widths of columns calculated in first draw and after size changes
pub widths: [usize; N], pub widths: [usize; N],
pub elasticities: [ColumnElasticity; N], pub elasticities: [ColumnElasticity; N],
@ -144,10 +147,11 @@ impl<const N: usize> Default for DataColumns<N> {
let ptr = &data as *const [MaybeUninit<T>; N]; let ptr = &data as *const [MaybeUninit<T>; N];
unsafe { (ptr as *const [T; N]).read() } unsafe { (ptr as *const [T; N]).read() }
} }
Self { Self {
cursor_config: TableCursorConfig::default(), cursor_config: TableCursorConfig::default(),
theme_config: TableThemeConfig::default(), theme_config: TableThemeConfig::default(),
columns: Box::new(init_array(CellBuffer::default)), columns: Box::new(init_array(|| Screen::init(Virtual))),
widths: [0_usize; N], widths: [0_usize; N],
elasticities: [ColumnElasticity::default(); N], elasticities: [ColumnElasticity::default(); N],
x_offset: 0, x_offset: 0,
@ -183,7 +187,7 @@ impl<const N: usize> DataColumns<N> {
self.widths[i] = self.widths[i] =
self.segment_tree[i].get_max(top_idx, top_idx + screen_height - 1) as usize; self.segment_tree[i].get_max(top_idx, top_idx + screen_height - 1) as usize;
if self.widths[i] == 0 { if self.widths[i] == 0 {
self.widths[i] = self.columns[i].cols; self.widths[i] = self.columns[i].cols();
} }
match self.elasticities[i] { match self.elasticities[i] {
ColumnElasticity::Rigid => {} ColumnElasticity::Rigid => {}
@ -191,11 +195,18 @@ impl<const N: usize> DataColumns<N> {
min, min,
max: Some(max), max: Some(max),
} => { } => {
self.widths[i] = std::cmp::max(min, std::cmp::min(max, self.widths[i])); if self.widths[i] < min {
self.widths[i] = min;
}
if self.widths[i] > max {
self.widths[i] = max;
}
growees += 1; growees += 1;
} }
ColumnElasticity::Grow { min, max: None } => { ColumnElasticity::Grow { min, max: None } => {
self.widths[i] = std::cmp::max(min, self.widths[i]); if self.widths[i] < min {
self.widths[i] = min;
}
growees += 1; growees += 1;
growees_max += 1; growees_max += 1;
} }
@ -240,7 +251,7 @@ impl<const N: usize> DataColumns<N> {
let mut skip_cols = (0, 0); let mut skip_cols = (0, 0);
let mut start_col = 0; let mut start_col = 0;
let total_area = bounds.area(); let total_area = bounds.area();
let (width, height) = (width!(total_area), height!(total_area)); let height = total_area.height();
while _relative_x_offset < self.x_offset && start_col < N { while _relative_x_offset < self.x_offset && start_col < N {
_relative_x_offset += self.widths[start_col] + 2; _relative_x_offset += self.widths[start_col] + 2;
if self.x_offset <= _relative_x_offset { if self.x_offset <= _relative_x_offset {
@ -258,45 +269,42 @@ impl<const N: usize> DataColumns<N> {
} }
let mut column_width = self.widths[col]; let mut column_width = self.widths[col];
if column_width > bounds.width { if column_width > bounds.width() {
column_width = bounds.width; column_width = bounds.width();
} else if column_width == 0 { } else if column_width == 0 {
skip_cols.1 = 0; skip_cols.1 = 0;
continue; continue;
} }
let mut column_area = bounds.add_x(column_width + 2);
grid.copy_area( grid.copy_area(
&self.columns[col], self.columns[col].grid(),
column_area.area(), bounds.area(),
( self.columns[col]
(skip_cols.1, top_idx), .area()
( .skip_rows(top_idx)
column_width.saturating_sub(1), .skip_cols(skip_cols.1)
self.columns[col].rows.saturating_sub(1), .take_cols(column_width),
),
),
); );
let gap_area = column_area.add_x(column_width); bounds.add_x(column_width + 2);
match self.theme_config.theme {
TableTheme::Single(row_attr) => {
grid.change_theme(gap_area.area(), row_attr);
}
TableTheme::EvenOdd { even, odd } => {
grid.change_theme(gap_area.area(), even);
let mut top_idx = top_idx;
for row in gap_area {
if top_idx % 2 != 0 {
grid.change_theme(row.area(), odd);
}
top_idx += 1;
}
}
};
skip_cols.1 = 0; skip_cols.1 = 0;
} }
match self.theme_config.theme {
TableTheme::Single(row_attr) => {
grid.change_theme(total_area, row_attr);
}
TableTheme::EvenOdd { even, odd } => {
grid.change_theme(total_area, even);
let mut top_idx = top_idx;
for row in 0..total_area.height() {
if top_idx % 2 != 0 {
grid.change_theme(total_area.nth_row(row), odd);
}
top_idx += 1;
}
}
}
if self.cursor_config.handle && (top_idx..(top_idx + height)).contains(&cursor_pos) { if self.cursor_config.handle && (top_idx..(top_idx + height)).contains(&cursor_pos) {
let offset = cursor_pos - top_idx; let offset = cursor_pos - top_idx;
let row_attr = match self.cursor_config.theme { let row_attr = match self.cursor_config.theme {
@ -305,13 +313,13 @@ impl<const N: usize> DataColumns<N> {
TableTheme::EvenOdd { even: _, odd } => odd, TableTheme::EvenOdd { even: _, odd } => odd,
}; };
grid.change_theme( grid.change_theme(total_area.skip_rows(offset).take_rows(1), row_attr);
( }
pos_inc(upper_left!(total_area), (0, offset)), }
pos_inc(upper_left!(total_area), (width, offset)),
), pub fn clear(&mut self) {
row_attr, for i in 0..N {
); self.columns[i].grid_mut().clear(None);
} }
} }
} }

View File

@ -92,8 +92,7 @@ impl TextField {
secondary_area: Area, secondary_area: Area,
context: &mut Context, context: &mut Context,
) { ) {
let upper_left = upper_left!(area); let width = area.width();
let width = width!(area);
let pos = if width < self.inner.grapheme_pos() { let pos = if width < self.inner.grapheme_pos() {
width width
} else { } else {
@ -101,7 +100,7 @@ impl TextField {
}; };
grid.change_colors( grid.change_colors(
(pos_inc(upper_left, (pos, 0)), pos_inc(upper_left, (pos, 0))), area.skip_cols(pos).take_cols(1),
crate::conf::value(context, "theme_default").fg, crate::conf::value(context, "theme_default").fg,
crate::conf::value(context, "highlight").bg, crate::conf::value(context, "highlight").bg,
); );
@ -119,7 +118,7 @@ impl TextField {
impl Component for TextField { impl Component for TextField {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let theme_attr = crate::conf::value(context, "widgets.form.field"); let theme_attr = crate::conf::value(context, "widgets.form.field");
let width = width!(area); let width = area.width();
let str = self.as_str(); let str = self.as_str();
/* Calculate which part of the str is visible /* Calculate which part of the str is visible
* ########################################## * ##########################################

View File

@ -347,19 +347,10 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> FormWidget<T>
impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for FormWidget<T> { impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for FormWidget<T> {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if self.is_dirty() { if self.is_dirty() {
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area( grid.clear_area(area.take_rows(self.layout.len()), theme_default);
(
upper_left,
set_y(bottom_right, get_y(upper_left) + self.layout.len()),
),
theme_default,
);
let label_attrs = crate::conf::value(context, "widgets.form.label"); let label_attrs = crate::conf::value(context, "widgets.form.label");
for (i, k) in self.layout.iter().enumerate().rev() { for (i, k) in self.layout.iter().enumerate().rev() {
@ -370,19 +361,13 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
label_attrs.fg, label_attrs.fg,
label_attrs.bg, label_attrs.bg,
label_attrs.attrs, label_attrs.attrs,
( area.nth_row(i).skip_cols(1),
pos_inc(upper_left, (1, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
None, None,
); );
/* draw field */ /* draw field */
v.draw( v.draw(
grid, grid,
( area.nth_row(i).skip_cols(self.field_name_max_length + 3),
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
context, context,
); );
@ -394,10 +379,9 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
if !context.settings.terminal.use_color() { if !context.settings.terminal.use_color() {
field_attrs.attrs |= Attr::REVERSE; field_attrs.attrs |= Attr::REVERSE;
} }
for row in grid.bounds_iter(( for row in grid
pos_inc(upper_left, (0, i)), .bounds_iter(area.nth_row(i).take_cols(area.width().saturating_sub(1)))
(get_x(bottom_right).saturating_sub(1), i + get_y(upper_left)), {
)) {
for c in row { for c in row {
grid[c] grid[c]
.set_fg(field_attrs.fg) .set_fg(field_attrs.fg)
@ -409,14 +393,9 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
if self.focus == FormFocus::TextInput { if self.focus == FormFocus::TextInput {
v.draw_cursor( v.draw_cursor(
grid, grid,
( area.nth_row(i).skip_cols(self.field_name_max_length + 3),
pos_inc(upper_left, (self.field_name_max_length + 3, i)), area.nth_row(i + 1)
(get_x(bottom_right), i + get_y(upper_left)), .skip_cols(self.field_name_max_length + 3),
),
(
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
bottom_right,
),
context, context,
); );
} }
@ -425,28 +404,13 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
let length = self.layout.len(); let length = self.layout.len();
grid.clear_area( grid.clear_area(area.skip_rows(length).take_rows(length + 2), theme_default);
(
pos_inc(upper_left, (0, length)),
set_y(bottom_right, length + 2 + get_y(upper_left)),
),
theme_default,
);
if !self.hide_buttons { if !self.hide_buttons {
self.buttons.draw( self.buttons
grid, .draw(grid, area.nth_row(length + 3).skip_cols(1), context);
(
pos_inc(upper_left, (1, length + 3)),
set_y(bottom_right, length + 3 + get_y(upper_left)),
),
context,
);
} }
if length + 4 < height!(area) { if length + 4 < area.height() {
grid.clear_area( grid.clear_area(area.skip_rows(length + 3), theme_default);
(pos_inc(upper_left, (0, length + 4)), bottom_right),
theme_default,
);
} }
self.set_dirty(false); self.set_dirty(false);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -646,7 +610,6 @@ where
if self.dirty { if self.dirty {
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(area, theme_default); grid.clear_area(area, theme_default);
let upper_left = upper_left!(area);
let mut len = 0; let mut len = 0;
for (i, k) in self.layout.iter().enumerate() { for (i, k) in self.layout.iter().enumerate() {
@ -660,10 +623,7 @@ where
theme_default.bg theme_default.bg
}, },
Attr::BOLD, Attr::BOLD,
( area.skip_cols(len).take_cols(cur_len + len),
pos_inc(upper_left, (len, 0)),
pos_inc(upper_left, (cur_len + len, 0)),
),
None, None,
); );
len += cur_len + 3; len += cur_len + 3;
@ -755,7 +715,6 @@ impl From<(String, String)> for AutoCompleteEntry {
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct AutoComplete { pub struct AutoComplete {
entries: Vec<AutoCompleteEntry>, entries: Vec<AutoCompleteEntry>,
content: CellBuffer,
cursor: usize, cursor: usize,
dirty: bool, dirty: bool,
@ -775,8 +734,7 @@ impl Component for AutoComplete {
}; };
self.dirty = false; self.dirty = false;
let (upper_left, bottom_right) = area; let rows = area.height();
let rows = get_y(bottom_right) - get_y(upper_left);
if rows == 0 { if rows == 0 {
return; return;
} }
@ -784,43 +742,62 @@ impl Component for AutoComplete {
let top_idx = page_no * rows; let top_idx = page_no * rows;
let x_offset = if rows < self.entries.len() { 1 } else { 0 }; let x_offset = if rows < self.entries.len() { 1 } else { 0 };
let (width, height) = self.content.size();
grid.clear_area(area, crate::conf::value(context, "theme_default")); grid.clear_area(area, crate::conf::value(context, "theme_default"));
let width = self
.entries
.iter()
.map(|a| a.entry.grapheme_len() + a.description.grapheme_len() + 2)
.max()
.unwrap_or(0)
+ 1;
// [ref:hardcoded_color_value]
let theme_attr = ThemeAttribute {
fg: Color::Byte(23),
bg: Color::Byte(7),
attrs: Attr::DEFAULT,
};
grid.change_theme(area, theme_attr);
for (i, e) in self.entries.iter().skip(top_idx).enumerate() {
let (x, _) = grid.write_string(
&e.entry,
Color::Byte(23),
Color::Byte(7),
Attr::DEFAULT,
area.nth_row(i).take_cols(width),
None,
);
grid.write_string(
&e.description,
Color::Byte(23),
Color::Byte(7),
Attr::ITALICS,
area.nth_row(i).skip_cols(x + 2).take_cols(width),
None,
);
grid.write_string(
"",
Color::Byte(23),
Color::Byte(7),
Attr::DEFAULT,
area.nth_row(i).skip_cols(width - 1),
None,
);
}
grid.copy_area(
&self.content,
(upper_left, pos_dec(bottom_right, (x_offset, 0))),
(
(0, top_idx),
(width.saturating_sub(1), height.saturating_sub(1)),
),
);
/* Highlight cursor */ /* Highlight cursor */
if self.cursor > 0 { if self.cursor > 0 {
let highlight = crate::conf::value(context, "highlight"); let highlight = crate::conf::value(context, "highlight");
grid.change_theme( grid.change_theme(
( area.nth_row((self.cursor - 1) % rows)
pos_inc(upper_left, (0, (self.cursor - 1) % rows)), .skip_cols(width.saturating_sub(1 + x_offset)),
(
std::cmp::min(
get_x(upper_left) + width.saturating_sub(1),
get_x(bottom_right),
)
.saturating_sub(x_offset),
get_y(pos_inc(upper_left, (0, (self.cursor - 1) % rows))),
),
),
highlight, highlight,
); );
} }
if rows < self.entries.len() { if rows < self.entries.len() {
ScrollBar { show_arrows: false }.draw( ScrollBar { show_arrows: false }.draw(
grid, grid,
( area.take_rows(x_offset),
set_y(pos_dec(bottom_right, (x_offset, 0)), get_y(upper_left)),
pos_dec(bottom_right, (x_offset, 0)),
),
context, context,
self.cursor.saturating_sub(1), self.cursor.saturating_sub(1),
rows, rows,
@ -829,12 +806,15 @@ impl Component for AutoComplete {
} }
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool { fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty self.dirty
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
self.dirty = value; self.dirty = value;
} }
@ -848,7 +828,6 @@ impl AutoComplete {
pub fn new(entries: Vec<AutoCompleteEntry>) -> Box<Self> { pub fn new(entries: Vec<AutoCompleteEntry>) -> Box<Self> {
let mut ret = AutoComplete { let mut ret = AutoComplete {
entries: Vec::new(), entries: Vec::new(),
content: CellBuffer::default(),
cursor: 0, cursor: 0,
dirty: true, dirty: true,
id: ComponentId::default(), id: ComponentId::default(),
@ -862,45 +841,6 @@ impl AutoComplete {
return false; return false;
} }
// [ref:hardcoded_color_value]
let mut content = CellBuffer::new(
entries
.iter()
.map(|a| a.entry.grapheme_len() + a.description.grapheme_len() + 2)
.max()
.unwrap_or(0)
+ 1,
entries.len(),
Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::DEFAULT),
);
let width = content.cols();
for (i, e) in entries.iter().enumerate() {
let (x, _) = content.write_string(
&e.entry,
Color::Byte(23),
Color::Byte(7),
Attr::DEFAULT,
((0, i), (width - 1, i)),
None,
);
content.write_string(
&e.description,
Color::Byte(23),
Color::Byte(7),
Attr::ITALICS,
((x + 2, i), (width - 1, i)),
None,
);
content.write_string(
"",
Color::Byte(23),
Color::Byte(7),
Attr::DEFAULT,
((width - 1, i), (width - 1, i)),
None,
);
}
self.content = content;
self.entries = entries; self.entries = entries;
self.cursor = 0; self.cursor = 0;
true true
@ -933,7 +873,6 @@ impl AutoComplete {
let ret = self.entries.remove(self.cursor - 1); let ret = self.entries.remove(self.cursor - 1);
self.entries.clear(); self.entries.clear();
self.cursor = 0; self.cursor = 0;
self.content.empty();
Some(ret.entry) Some(ret.entry)
} }
@ -965,7 +904,7 @@ impl ScrollBar {
if length == 0 { if length == 0 {
return; return;
} }
let height = height!(area); let height = area.height();
if height < 3 { if height < 3 {
return; return;
} }
@ -977,21 +916,21 @@ impl ScrollBar {
let ratio: f64 = (height as f64) / (length as f64); let ratio: f64 = (height as f64) / (length as f64);
let scrollbar_height = std::cmp::max((ratio * (visible_rows as f64)) as usize, 1); let scrollbar_height = std::cmp::max((ratio * (visible_rows as f64)) as usize, 1);
let scrollbar_offset = (ratio * (pos as f64)) as usize; let scrollbar_offset = (ratio * (pos as f64)) as usize;
let (mut upper_left, bottom_right) = area; let mut area2 = area;
if self.show_arrows { if self.show_arrows {
grid[upper_left] grid[area2.upper_left()]
.set_ch(if ascii_drawing { '^' } else { '▀' }) .set_ch(if ascii_drawing { '^' } else { '▀' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg); .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (0, 1)); area2 = area2.skip_rows(1);
} }
upper_left = pos_inc(upper_left, (0, scrollbar_offset)); area2 = area2.skip_rows(scrollbar_offset);
for _ in 0..scrollbar_height { for _ in 0..scrollbar_height {
if get_y(upper_left) >= get_y(bottom_right) { if area2.is_empty() {
break; break;
} }
grid[upper_left] grid[area2.upper_left()]
.set_ch(if ascii_drawing { '#' } else { '█' }) .set_ch(if ascii_drawing { '#' } else { '█' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_attrs(if !context.settings.terminal.use_color() { .set_attrs(if !context.settings.terminal.use_color() {
@ -999,10 +938,10 @@ impl ScrollBar {
} else { } else {
theme_default.attrs theme_default.attrs
}); });
upper_left = pos_inc(upper_left, (0, 1)); area2 = area2.skip_rows(1);
} }
if self.show_arrows { if self.show_arrows {
grid[bottom_right] grid[area2.bottom_right()]
.set_ch(if ascii_drawing { 'v' } else { '▄' }) .set_ch(if ascii_drawing { 'v' } else { '▄' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg); .set_bg(crate::conf::value(context, "theme_default").bg);
@ -1021,7 +960,7 @@ impl ScrollBar {
if length == 0 { if length == 0 {
return; return;
} }
let width = width!(area); let width = area.width();
if width < 3 { if width < 3 {
return; return;
} }
@ -1033,21 +972,21 @@ impl ScrollBar {
let ratio: f64 = (width as f64) / (length as f64); let ratio: f64 = (width as f64) / (length as f64);
let scrollbar_width = std::cmp::min((ratio * (visible_cols as f64)) as usize, 1); let scrollbar_width = std::cmp::min((ratio * (visible_cols as f64)) as usize, 1);
let scrollbar_offset = (ratio * (pos as f64)) as usize; let scrollbar_offset = (ratio * (pos as f64)) as usize;
let (mut upper_left, bottom_right) = area; let mut area2 = area;
if self.show_arrows { if self.show_arrows {
grid[upper_left] grid[area2.upper_left()]
.set_ch(if ascii_drawing { '<' } else { '▐' }) .set_ch(if ascii_drawing { '<' } else { '▐' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg); .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (1, 0)); area2 = area2.skip_cols(1);
} }
upper_left = pos_inc(upper_left, (scrollbar_offset, 0)); area2 = area2.skip_cols(scrollbar_offset);
for _ in 0..scrollbar_width { for _ in 0..scrollbar_width {
if get_x(upper_left) >= get_x(bottom_right) { if area2.is_empty() {
break; break;
} }
grid[upper_left] grid[area2.upper_left()]
.set_ch(if ascii_drawing { '#' } else { '█' }) .set_ch(if ascii_drawing { '#' } else { '█' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_attrs(if !context.settings.terminal.use_color() { .set_attrs(if !context.settings.terminal.use_color() {
@ -1055,10 +994,10 @@ impl ScrollBar {
} else { } else {
theme_default.attrs theme_default.attrs
}); });
upper_left = pos_inc(upper_left, (1, 0)); area2 = area2.skip_cols(1);
} }
if self.show_arrows { if self.show_arrows {
grid[bottom_right] grid[area2.bottom_right()]
.set_ch(if ascii_drawing { '>' } else { '▌' }) .set_ch(if ascii_drawing { '>' } else { '▌' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg); .set_bg(crate::conf::value(context, "theme_default").bg);

View File

@ -127,7 +127,7 @@ impl Component for EmbedContainer {
embed_area, embed_area,
((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))), ((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
); );
guard.set_terminal_size((width!(embed_area), height!(embed_area))); guard.set_terminal_size((embed_area.width(), embed_area.height()));
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
self.dirty = false; self.dirty = false;
return; return;
@ -160,12 +160,12 @@ impl Component for EmbedContainer {
let inner_area = create_box( let inner_area = create_box(
grid, grid,
( (
pos_inc(upper_left!(area), (1, 0)), pos_inc(area.upper_left(), (1, 0)),
pos_inc( pos_inc(
upper_left!(area), area.upper_left(),
( (
std::cmp::min(max_len + 5, width!(area)), std::cmp::min(max_len + 5, area.width()),
std::cmp::min(5, height!(area)), std::cmp::min(5, area.height()),
), ),
), ),
), ),
@ -185,10 +185,10 @@ impl Component for EmbedContainer {
theme_default.bg, theme_default.bg,
theme_default.attrs, theme_default.attrs,
( (
pos_inc((0, i), upper_left!(inner_area)), pos_inc((0, i), inner_area.upper_left()),
bottom_right!(inner_area), inner_area.bottom_right(),
), ),
Some(get_x(upper_left!(inner_area))), Some(get_x(inner_area.upper_left())),
); );
} }
} }
@ -196,10 +196,10 @@ impl Component for EmbedContainer {
} else { } else {
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(area, theme_default); grid.clear_area(area, theme_default);
self.embed_area = (upper_left!(area), bottom_right!(area)); self.embed_area = (area.upper_left(), area.bottom_right());
match create_pty( match create_pty(
width!(self.embed_area), self.embed_area.width(),
height!(self.embed_area), self.embed_area.height(),
self.command.clone(), self.command.clone(),
) { ) {
Ok(embed) => { Ok(embed) => {