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

View File

@ -147,8 +147,8 @@ impl Component for ContactManager {
self.initialized = true;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let upper_left = area.upper_left();
let bottom_right = area.bottom_right();
if self.dirty {
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)),
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;
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -86,7 +86,7 @@ impl Component for KeySelection {
KeySelection::LoadingKeys {
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, .. } => {
let theme_default = crate::conf::value(context, "theme_default");
grid.write_string(
@ -94,7 +94,7 @@ impl Component for KeySelection {
theme_default.fg,
theme_default.bg,
theme_default.attrs,
center_area(area, (15, 2)),
area.center_inside((15, 2)),
Some(0),
);
}

View File

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

View File

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

View File

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

View File

@ -26,22 +26,6 @@ use melib::{Address, SortField, SortOrder, ThreadNode};
use super::{EntryStrings, *};
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 {
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
let color_cache = &$color_cache;
@ -210,6 +194,7 @@ impl MailListingTrait for PlainListing {
/// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true);
self.force_draw = true;
let old_cursor_pos = self.cursor_pos;
if !(self.cursor_pos.0 == self.new_cursor_pos.0
&& 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) {
Ok(()) => {}
Err(_) => {
self.length = 0;
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0;
self.data_columns.columns[0].write_string(
message.as_str(),
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len().saturating_sub(1), 0)),
None,
);
if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) {
let area = self.data_columns.columns[0].area();
self.data_columns.columns[0].grid_mut().write_string(
message.as_str(),
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
area,
None,
);
}
return;
}
}
@ -338,6 +324,7 @@ impl ListingTrait for PlainListing {
self.filtered_order.clear();
self.filter_term.clear();
self.rows.row_updates.clear();
self.data_columns.clear();
}
fn next_entry(&mut self, context: &mut Context) {
@ -390,17 +377,12 @@ impl ListingTrait for PlainListing {
self.rows.selection[&i]
);
let (upper_left, bottom_right) = area;
let x = get_x(upper_left)
+ self.data_columns.widths[0]
let x = self.data_columns.widths[0]
+ self.data_columns.widths[1]
+ self.data_columns.widths[2]
+ 3 * 2;
for c in grid.row_iter(
get_x(upper_left)..(get_x(bottom_right) + 1),
get_y(upper_left),
) {
for c in grid.row_iter(area, 0..area.width(), 0) {
grid[c]
.set_fg(row_attr.fg)
.set_bg(row_attr.bg)
@ -408,14 +390,11 @@ impl ListingTrait for PlainListing {
}
grid.copy_area(
&self.data_columns.columns[3],
(set_x(upper_left, x), bottom_right),
(
(0, idx),
pos_dec(self.data_columns.columns[3].size(), (1, 1)),
),
self.data_columns.columns[3].grid(),
area.skip_cols(x),
self.data_columns.columns[3].area().nth_row(idx),
);
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);
}
}
@ -426,20 +405,18 @@ impl ListingTrait for PlainListing {
{
self.refresh_mailbox(context, false);
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if self.length == 0 {
grid.clear_area(area, self.color_cache.theme_default);
grid.copy_area(
&self.data_columns.columns[0],
self.data_columns.columns[0].grid(),
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);
return;
}
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
let rows = area.height();
if rows == 0 {
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 page_no = (self.new_cursor_pos.2).wrapping_div(rows);
@ -492,7 +473,7 @@ impl ListingTrait for PlainListing {
if idx >= self.length {
continue; //bounds check
}
let new_area = nth_row_area(area, idx % rows);
let new_area = area.nth_row(idx % rows);
self.data_columns
.draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
if highlight {
@ -514,18 +495,18 @@ impl ListingTrait for PlainListing {
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 */
_ = self
.data_columns
.recalc_widths((width!(area), height!(area)), top_idx);
grid.clear_area(area, self.color_cache.theme_default);
_ = self.data_columns.recalc_widths(area.size(), top_idx);
/* copy table columns */
self.data_columns
.draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
/* 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) {
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,
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 */
if top_idx + rows > self.length {
grid.clear_area(
(
pos_inc(upper_left, (0, self.length - top_idx)),
bottom_right,
),
grid.change_theme(
area.skip_rows(self.length - top_idx),
self.color_cache.theme_default,
);
}
self.force_draw = false;
context.dirty_areas.push_back(area);
}
@ -589,7 +569,7 @@ impl ListingTrait for PlainListing {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} 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(
context,
@ -867,7 +847,7 @@ impl PlainListing {
self.data_columns.elasticities[0].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
.cursor_config
@ -881,17 +861,13 @@ impl PlainListing {
.set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
_ = self.data_columns.columns[0].resize_with_context(min_width.0, self.rows.len(), context);
/* date column */
self.data_columns.columns[1] =
CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
_ = self.data_columns.columns[1].resize_with_context(min_width.1, self.rows.len(), context);
/* from column */
self.data_columns.columns[2] =
CellBuffer::new_with_context(min_width.2, self.rows.len(), None, context);
_ = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context);
/* subject column */
self.data_columns.columns[3] =
CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
_ = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context);
let iter = if self.filter_term.is_empty() {
Box::new(self.local_collection.iter().cloned())
@ -912,104 +888,157 @@ impl PlainListing {
//);
//log::debug!("{:#?}", context.accounts);
panic!();
continue;
}
let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = columns[0].write_string(
&idx.to_string(),
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((0, idx), (min_width.0, idx)),
None,
);
for c in columns[0].row_iter(x..min_width.0, idx) {
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
let (x, _) = {
let area = columns[0].area().nth_row(idx);
columns[0].grid_mut().write_string(
&idx.to_string(),
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
for c in columns[3].row_iter(x..min_width.3, idx) {
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
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);
}
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() {
let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length + 1, None, context);
self.data_columns.columns[0].write_string(
&message,
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)),
None,
);
if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) {
let area = self.data_columns.columns[0].area();
self.data_columns.columns[0].grid_mut().write_string(
message.as_str(),
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
area,
None,
);
}
}
}
@ -1044,97 +1073,152 @@ impl PlainListing {
let strings = self.make_entry_string(&envelope, context);
drop(envelope);
let columns = &mut self.data_columns.columns;
let min_width = (
columns[0].size().0,
columns[1].size().0,
columns[2].size().0,
columns[3].size().0,
);
{
let area = columns[0].area().nth_row(idx);
columns[0].grid_mut().clear_area(area, row_attr)
};
{
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);
columns[1].clear_area(((0, idx), (min_width.1, idx)), row_attr);
columns[2].clear_area(((0, idx), (min_width.2, idx)), row_attr);
columns[3].clear_area(((0, idx), (min_width.3, idx)), row_attr);
let (x, _) = columns[0].write_string(
&idx.to_string(),
row_attr.fg,
row_attr.bg,
row_attr.attrs,
((0, idx), (min_width.0, idx)),
None,
);
for c in columns[0].row_iter(x..min_width.0, idx) {
columns[0][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
let (x, _) = {
let area = columns[0].area().nth_row(idx);
columns[0].grid_mut().write_string(
&idx.to_string(),
row_attr.fg,
row_attr.bg,
row_attr.attrs,
area,
None,
)
};
for c in {
let area = columns[0].area();
columns[0].grid_mut().row_iter(area, x..area.width(), idx)
} {
columns[0].grid_mut()[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, _) = {
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..area.width(), idx)
} {
columns[1].grid_mut()[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, _) = {
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..area.width(), idx)
} {
columns[2].grid_mut()[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 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,
)
};
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);
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,
)
};
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) {
columns[3][c].set_bg(color).set_keep_bg(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_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 {
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) {
columns[3][c].set_keep_bg(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 + 1;
x = _x + 2;
}
x
};
for c in columns[3].row_iter(x..min_width.3, idx) {
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
for c in {
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);
}
@ -1154,7 +1238,6 @@ impl Component for PlainListing {
if matches!(self.focus, Focus::None) {
let mut area = area;
if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area;
let (x, y) = grid.write_string(
&format!(
"{} 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.attrs,
area,
Some(get_x(upper_left)),
Some(0),
);
grid.clear_area(
((x, y), set_y(bottom_right, y)),
self.color_cache.theme_default,
);
context
.dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 1)));
grid.clear_area(area.skip(x, y).nth_row(y), self.color_cache.theme_default);
context.dirty_areas.push_back(area);
area = (set_y(upper_left, y + 1), bottom_right);
area = area.skip_rows(y + 1);
}
let (upper_left, bottom_right) = area;
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
let rows = area.height();
if let Some(modifier) = self.modifier_command.take() {
if let Some(mvm) = self.movement.as_ref() {
@ -1379,13 +1456,13 @@ impl Component for PlainListing {
self.draw_list(grid, area, context);
}
} else {
self.view_area = area.into();
if self.length == 0 && self.dirty {
grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area);
self.dirty = false;
return;
}
self.view_area = area.into();
}
self.dirty = false;
}
@ -1394,6 +1471,11 @@ impl Component for PlainListing {
let shortcuts = self.shortcuts(context);
match (&event, self.focus) {
(UIEvent::VisibilityChange(true), _) => {
self.force_draw = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref k), Focus::Entry)
if shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"]) =>
{
@ -1493,7 +1575,7 @@ impl Component for PlainListing {
UIEvent::MailboxUpdate((ref idxa, ref idxf))
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);
}
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
@ -1617,10 +1699,11 @@ impl Component for PlainListing {
}
fn is_dirty(&self) -> bool {
match self.focus {
Focus::None => self.dirty,
Focus::Entry | Focus::EntryFullscreen => false,
}
self.force_draw
|| match self.focus {
Focus::None => self.dirty,
Focus::Entry | Focus::EntryFullscreen => false,
}
}
fn set_dirty(&mut self, value: bool) {

View File

@ -489,8 +489,8 @@ impl ListingTrait for ThreadListing {
{
self.refresh_mailbox(context, false);
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let upper_left = area.upper_left();
let bottom_right = area.bottom_right();
if self.length == 0 {
grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area);
@ -551,7 +551,7 @@ impl ListingTrait for ThreadListing {
if idx >= self.length {
continue; //bounds check
}
let new_area = nth_row_area(area, idx % rows);
let new_area = area.nth_row(idx % rows);
self.data_columns
.draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
if let Some(env_hash) = self.get_env_under_cursor(idx) {
@ -589,7 +589,7 @@ impl ListingTrait for ThreadListing {
_ = self
.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);
/* copy table columns */
self.data_columns
@ -598,7 +598,7 @@ impl ListingTrait for ThreadListing {
self.draw_relative_numbers(grid, area, top_idx);
}
/* 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) {
let row_attr = row_attr!(
self.color_cache,
@ -607,7 +607,7 @@ impl ListingTrait for ThreadListing {
self.cursor_pos.2 == idx,
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,
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 */
@ -1110,8 +1110,8 @@ impl ThreadListing {
fn draw_relative_numbers(&mut self, grid: &mut CellBuffer, area: Area, top_idx: usize) {
let width = self.data_columns.columns[0].size().0;
let upper_left = upper_left!(area);
for i in 0..height!(area) {
let upper_left = area.upper_left();
for i in 0..area.height() {
let row_attr = if let Some(env_hash) = self.get_env_under_cursor(top_idx + i) {
row_attr!(
self.color_cache,
@ -1366,8 +1366,8 @@ impl Component for ThreadListing {
self.draw_list(grid, area, context);
} else {
self.cursor_pos = self.new_cursor_pos;
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let upper_left = area.upper_left();
let bottom_right = area.bottom_right();
if self.length == 0 && self.dirty {
grid.clear_area(area, self.color_cache.theme_default);
context.dirty_areas.push_back(area);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ impl Default 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 {
return;
}
@ -89,7 +89,7 @@ impl Component for SVGScreenshotFilter {
/* keep a map with used colors and write a stylesheet when we're done */
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();
escaped_text.clear();
/* 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};
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::*};

File diff suppressed because it is too large Load Diff

View File

@ -42,8 +42,6 @@ use nix::{
};
use smallvec::SmallVec;
use crate::terminal::position::*;
mod grid;
#[cfg(not(target_os = "macos"))]
@ -54,7 +52,7 @@ use std::{
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
// process"
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 {
ExpectingControlChar,
G0, // Designate G0 Character Set

View File

@ -26,11 +26,13 @@ use melib::{
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use super::*;
use crate::terminal::{cells::*, Color};
use crate::terminal::{cells::*, Area, Color, Screen, Virtual};
#[derive(Debug)]
enum ScreenBuffer {
Normal,
#[derive(Debug, Default, Clone, Copy)]
#[repr(u8)]
pub enum ScreenBuffer {
#[default]
Normal = 0,
Alternate,
}
@ -41,15 +43,15 @@ enum ScreenBuffer {
/// 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.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct EmbedGrid {
cursor: (usize, usize),
/// `[top;bottom]`
scroll_region: ScrollRegion,
pub alternate_screen: CellBuffer,
pub alternate_screen: Box<Screen<Virtual>>,
pub state: State,
/// (width, height)
pub terminal_size: (usize, usize),
terminal_size: (usize, usize),
initialized: bool,
fg_color: Color,
bg_color: Color,
@ -69,7 +71,7 @@ pub struct EmbedGrid {
wrap_next: bool,
/// Store state in case a multi-byte character is encountered
codepoints: CodepointBuf,
pub normal_screen: CellBuffer,
pub normal_screen: Box<Screen<Virtual>>,
screen_buffer: ScreenBuffer,
}
@ -177,7 +179,7 @@ impl EmbedTerminal {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CodepointBuf {
None,
TwoCodepoints(u8),
@ -187,8 +189,7 @@ enum CodepointBuf {
impl EmbedGrid {
pub fn new() -> Self {
let mut normal_screen = CellBuffer::default();
normal_screen.set_growable(true);
let normal_screen = Box::new(Screen::<Virtual>::new());
EmbedGrid {
cursor: (0, 0),
scroll_region: ScrollRegion {
@ -199,7 +200,7 @@ impl EmbedGrid {
},
terminal_size: (0, 0),
initialized: false,
alternate_screen: CellBuffer::default(),
alternate_screen: Box::new(Screen::<Virtual>::new()),
state: State::Normal,
fg_color: Color::Default,
bg_color: Color::Default,
@ -220,15 +221,15 @@ impl EmbedGrid {
pub fn buffer(&self) -> &CellBuffer {
match self.screen_buffer {
ScreenBuffer::Normal => &self.normal_screen,
ScreenBuffer::Alternate => &self.alternate_screen,
ScreenBuffer::Normal => self.normal_screen.grid(),
ScreenBuffer::Alternate => self.alternate_screen.grid(),
}
}
pub fn buffer_mut(&mut self) -> &mut CellBuffer {
match self.screen_buffer {
ScreenBuffer::Normal => &mut self.normal_screen,
ScreenBuffer::Alternate => &mut self.alternate_screen,
ScreenBuffer::Normal => self.normal_screen.grid_mut(),
ScreenBuffer::Alternate => self.alternate_screen.grid_mut(),
}
}
@ -236,30 +237,32 @@ impl EmbedGrid {
if new_val == self.terminal_size && self.initialized {
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.scroll_region.top = 0;
self.scroll_region.bottom = new_val.1.saturating_sub(1);
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.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) {
let area = self.area();
let EmbedGrid {
ref mut cursor,
ref mut scroll_region,
@ -282,12 +285,12 @@ impl EmbedGrid {
ref mut normal_screen,
initialized: _,
} = self;
let mut grid = normal_screen;
let mut grid = normal_screen.grid_mut();
let is_alternate = match *screen_buffer {
ScreenBuffer::Normal => false,
_ => {
grid = alternate_screen;
grid = alternate_screen.grid_mut();
true
}
};
@ -661,19 +664,10 @@ impl EmbedGrid {
/* Erase Below (default). */
grid.clear_area(
(
(
0,
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),
),
),
area.skip_rows(std::cmp::min(
cursor.1 + 1 + scroll_region.top,
terminal_size.1.saturating_sub(1),
)),
Default::default(),
);
//log::trace!("{}", EscCode::from((&(*state), byte)));
@ -759,7 +753,7 @@ impl EmbedGrid {
//log::trace!("{}", EscCode::from((&(*state), byte)));
grid.clear_area(
((0, 0), pos_dec(*terminal_size, (1, 1))),
area.take_cols(terminal_size.0).take_rows(terminal_size.1),
Default::default(),
);
*state = State::Normal;
@ -769,19 +763,10 @@ impl EmbedGrid {
/* Erase Below (default). */
grid.clear_area(
(
(
0,
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),
),
),
area.skip_rows(std::cmp::min(
cursor.1 + 1 + scroll_region.top,
terminal_size.1.saturating_sub(1),
)),
Default::default(),
);
//log::trace!("{}", EscCode::from((&(*state), byte)));
@ -792,13 +777,7 @@ impl EmbedGrid {
/* Erase Above */
grid.clear_area(
(
(0, 0),
(
terminal_size.0.saturating_sub(1),
cursor.1.saturating_sub(1) + scroll_region.top,
),
),
area.take_rows(cursor.1.saturating_sub(1) + scroll_region.top),
Default::default(),
);
//log::trace!("{}", EscCode::from((&(*state), byte)));
@ -808,10 +787,7 @@ impl EmbedGrid {
/* Erase in Display (ED), VT100. */
/* Erase All */
grid.clear_area(
((0, 0), pos_dec(*terminal_size, (1, 1))),
Default::default(),
);
grid.clear_area(area, Default::default());
//log::trace!("{}", EscCode::from((&(*state), byte)));
*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))
}
/// 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)]
pub enum Alignment {
/// Stretch to fill all space if possible, center if no meaningful way to
@ -191,78 +65,3 @@ pub enum Alignment {
#[default]
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,
entries: Vec<(T, bool)>,
entry_titles: Vec<String>,
pub content: CellBuffer,
theme_default: ThemeAttribute,
cursor: SelectorCursor,
@ -114,7 +113,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
return false;
}
let (width, height) = self.content.size();
let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
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
* cursor */
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;
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 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;
} 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.dirty = true;
@ -216,34 +182,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
{
if self.single_only {
// 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].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.dirty = true;
@ -253,23 +193,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
| (UIEvent::Input(ref key), SelectorCursor::Cancel)
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);
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;
return true;
}
@ -279,34 +204,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
{
if self.single_only {
// 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].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.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"]) =>
{
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;
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"]) =>
{
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;
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"]) =>
{
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;
return true;
}
@ -439,7 +281,6 @@ impl Component for UIConfirmationDialog {
return false;
}
let (width, height) = self.content.size();
let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
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
* cursor */
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;
return true;
}
@ -518,34 +340,8 @@ impl Component for UIConfirmationDialog {
{
if self.single_only {
// 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].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.dirty = true;
@ -555,23 +351,8 @@ impl Component for UIConfirmationDialog {
| (UIEvent::Input(ref key), SelectorCursor::Cancel)
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);
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;
return true;
}
@ -579,20 +360,7 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{
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;
} 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.dirty = true;
@ -604,34 +372,8 @@ impl Component for UIConfirmationDialog {
{
if self.single_only {
// 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].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.dirty = true;
@ -642,22 +384,6 @@ impl Component for UIConfirmationDialog {
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{
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;
return true;
}
@ -665,26 +391,6 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{
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;
return true;
}
@ -692,27 +398,6 @@ impl Component for UIConfirmationDialog {
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{
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;
return true;
}
@ -761,7 +446,7 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
single_only: bool,
done_fn: F,
context: &Context,
) -> Selector<T, F> {
) -> Self {
let entry_titles = entries
.iter_mut()
.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;
}
let mut ret = Selector {
let mut ret = Self {
single_only,
entries: identifiers,
entry_titles,
content: Default::default(),
cursor: SelectorCursor::Unfocused,
vertical_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) {
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 {
@ -871,18 +501,17 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
Key::Char('\n')
);
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,
) + 3;
let height = self.content.size().1 + {
let height = self.entries.len() + {
/* padding */
3
};
let dialog_area = align_area(
area,
let dialog_area = area.align_inside(
(width, height),
self.vertical_alignment,
self.horizontal_alignment,
self.vertical_alignment,
);
let inner_area = create_box(grid, dialog_area);
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.bg,
self.theme_default.attrs | Attr::BOLD,
(
pos_inc(upper_left!(dialog_area), (2, 0)),
bottom_right!(dialog_area),
),
dialog_area.skip_cols(2),
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.bg,
self.theme_default.attrs | Attr::ITALICS,
(
pos_inc(upper_left!(dialog_area), (2, height)),
bottom_right!(dialog_area),
),
dialog_area.skip_cols(2).skip_rows(height),
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);
self.dirty = false;
}

View File

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

View File

@ -22,6 +22,7 @@
use melib::text_processing::{LineBreakText, Truncate};
use super::*;
use crate::terminal::embed::EmbedGrid;
/// A pager for text.
/// `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
/// events.
rows_lt_height: bool,
filtered_content: Option<(String, Result<CellBuffer>)>,
filtered_content: Option<(String, Result<EmbedGrid>)>,
text_lines: Vec<String>,
line_breaker: LineBreakText,
movement: Option<PageMovement>,
@ -180,7 +181,7 @@ impl Pager {
}
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::{
io::Write,
process::{Command, Stdio},
@ -200,16 +201,16 @@ impl Pager {
.chain_err_summary(|| "Failed to wait on filter")?
.stdout;
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));
for b in out {
embedded.process_byte(&mut dev_null, b);
}
Ok(std::mem::take(embedded.buffer_mut()))
Ok(embedded)
};
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.height = height;
}
@ -225,7 +226,7 @@ impl Pager {
}
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 {
width = self.minimum_width;
}
@ -253,7 +254,7 @@ impl Pager {
);
}
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);
}
}
@ -262,7 +263,7 @@ impl Pager {
grid,
area,
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);
@ -284,10 +285,10 @@ impl Pager {
if up_to == 0 {
self.text_lines.extend(self.line_breaker.by_ref());
} else {
if old_lines_no >= up_to + height!(area) {
if old_lines_no >= up_to + area.height() {
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
.extend(self.line_breaker.by_ref().take(new_lines_no));
};
@ -310,21 +311,14 @@ impl Pager {
match filtered_content {
Ok(ref content) => {
grid.copy_area(
content,
content.buffer(),
area,
(
(
std::cmp::min(
self.cursor.0,
content.size().0.saturating_sub(width!(area)),
),
std::cmp::min(
self.cursor.1,
content.size().1.saturating_sub(height!(area)),
),
),
pos_dec(content.size(), (1, 1)),
),
content
.area()
.skip_cols(self.cursor.0)
.skip_rows(self.cursor.1)
.take_cols(content.terminal_size().0.saturating_sub(area.width()))
.take_rows(content.terminal_size().1.saturating_sub(area.height())),
);
context
.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(
l,
self.colors.fg,
self.colors.bg,
Attr::DEFAULT,
(upper_left, bottom_right),
None,
);
if l.starts_with('⤷') {
grid[upper_left]
.set_fg(crate::conf::value(context, "highlight").fg)
.set_attrs(crate::conf::value(context, "highlight").attrs);
let mut area2 = area;
for l in self
.text_lines
.iter()
.skip(self.cursor.1)
.take(area2.height())
{
if area2.is_empty() {
break;
}
grid.write_string(
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") {
let t = grid.insert_tag(text_formatter.tag);
for (i, l) in self
.text_lines
.iter()
.skip(self.cursor.1)
.enumerate()
.take(height!(area) + 1)
#[cfg(feature = "regexp")]
{
let area3 = area;
for text_formatter in
crate::conf::text_format_regexps(context, "pager.envelope.body")
{
let i = i + get_y(upper_left);
for (start, end) in text_formatter.regexp.find_iter(l) {
let start = start + get_x(upper_left);
let end = end + get_x(upper_left);
grid.set_tag(t, (start, i), (end, i));
let t = grid.insert_tag(text_formatter.tag);
for (i, l) in self
.text_lines
.iter()
.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));
}
}
}
}
}
let cursor_line = self.cursor.1;
if let Some(ref mut search) = self.search {
let results_attr = crate::conf::value(context, "pager.highlight_search");
let results_current_attr =
crate::conf::value(context, "pager.highlight_search_current");
search.cursor = std::cmp::min(search.positions.len().saturating_sub(1), search.cursor);
for (i, (y, x)) in search
.positions
.iter()
.enumerate()
.filter(|(_, (y, _))| *y >= cursor_line)
.take(height!(area) + 1)
{
let x = *x + get_x(upper_left);
let y = *y - cursor_line;
for c in grid.row_iter(
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);
if let Some(ref mut search) = self.search {
// Last row will be reserved for the "Results for ..." line.
let area3 = area.skip_rows_from_end(1);
let cursor_line = self.cursor.1;
let results_attr = crate::conf::value(context, "pager.highlight_search");
let results_current_attr =
crate::conf::value(context, "pager.highlight_search_current");
search.cursor =
std::cmp::min(search.positions.len().saturating_sub(1), search.cursor);
for (i, (y, offset)) in search
.positions
.iter()
.enumerate()
.filter(|(_, &(y, _))| y >= cursor_line && y < cursor_line + area3.height())
{
let attr = if i == search.cursor {
results_current_attr
} 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]
.set_fg(results_attr.fg)
.set_bg(results_attr.bg)
.set_attrs(results_attr.attrs);
.set_fg(attr.fg)
.set_bg(attr.bg)
.set_attrs(attr.attrs);
}
}
}
@ -435,9 +438,6 @@ impl Pager {
impl Component for Pager {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) {
return;
}
if !self.is_dirty() {
return;
}
@ -448,14 +448,42 @@ impl Component for Pager {
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() {
match mvm {
PageMovement::Up(amount) => {
self.cursor.1 = self.cursor.1.saturating_sub(amount);
}
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) => {
if self.cursor.1 + amount + 1 < self.height {
@ -467,22 +495,22 @@ impl Component for Pager {
grid,
area,
context,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * height,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * rows,
);
}
PageMovement::PageDown(multiplier) => {
if self.cursor.1 + height * multiplier + 1 < self.height {
self.cursor.1 += height * multiplier;
} else if self.cursor.1 + height * multiplier > self.height {
if self.cursor.1 + rows * multiplier + 1 < self.height {
self.cursor.1 += rows * multiplier;
} else if self.cursor.1 + rows * multiplier > self.height {
self.cursor.1 = self.height.saturating_sub(1);
} else {
self.cursor.1 = (self.height / height) * height;
self.cursor.1 = (self.height / rows) * rows;
}
self.draw_lines_up_to(
grid,
area,
context,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * height,
self.cursor.1 + Self::PAGES_AHEAD_TO_RENDER_NO * rows,
);
}
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 !search.positions.is_empty() {
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"));
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.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 = (
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
);
self.draw_page(
grid,
(upper_left!(area), pos_inc(upper_left!(area), (cols, rows))),
context,
);
self.draw_page(grid, area.take_cols(cols).take_rows(rows), context);
if self.show_scrollbar && rows < height {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
set_x(upper_left!(area), get_x(bottom_right!(area))),
bottom_right!(area),
),
area.nth_col(area.width()),
context,
/* position */
self.cursor.1,
@ -584,10 +579,7 @@ impl Component for Pager {
if self.show_scrollbar && cols < width {
ScrollBar::default().set_show_arrows(true).draw_horizontal(
grid,
(
set_y(upper_left!(area), get_y(bottom_right!(area))),
bottom_right!(area),
),
area.nth_row(area.height()),
context,
self.cursor.0,
cols,
@ -635,23 +627,26 @@ impl Component for Pager {
if !context.settings.terminal.use_color() {
attribute.attrs |= Attr::REVERSE;
}
let (_, y) = grid.write_string(
grid.write_string(
&status_message,
attribute.fg,
attribute.bg,
attribute.attrs,
(
set_y(upper_left!(area), get_y(bottom_right!(area))),
bottom_right!(area),
),
area.nth_row(area.height().saturating_sub(1)),
None,
);
/* set search pattern to italics */
let start_x = get_x(upper_left!(area)) + RESULTS_STR.len();
for c in grid.row_iter(start_x..(start_x + search.pattern.grapheme_width()), y) {
let start_x = RESULTS_STR.len();
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);
}
};
}
}
context.dirty_areas.push_back(area);
}

View File

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

View File

@ -92,8 +92,7 @@ impl TextField {
secondary_area: Area,
context: &mut Context,
) {
let upper_left = upper_left!(area);
let width = width!(area);
let width = area.width();
let pos = if width < self.inner.grapheme_pos() {
width
} else {
@ -101,7 +100,7 @@ impl TextField {
};
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, "highlight").bg,
);
@ -119,7 +118,7 @@ impl TextField {
impl Component for TextField {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let theme_attr = crate::conf::value(context, "widgets.form.field");
let width = width!(area);
let width = area.width();
let str = self.as_str();
/* 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> {
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() {
let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(
(
upper_left,
set_y(bottom_right, get_y(upper_left) + self.layout.len()),
),
theme_default,
);
grid.clear_area(area.take_rows(self.layout.len()), theme_default);
let label_attrs = crate::conf::value(context, "widgets.form.label");
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.bg,
label_attrs.attrs,
(
pos_inc(upper_left, (1, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
area.nth_row(i).skip_cols(1),
None,
);
/* draw field */
v.draw(
grid,
(
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
area.nth_row(i).skip_cols(self.field_name_max_length + 3),
context,
);
@ -394,10 +379,9 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
if !context.settings.terminal.use_color() {
field_attrs.attrs |= Attr::REVERSE;
}
for row in grid.bounds_iter((
pos_inc(upper_left, (0, i)),
(get_x(bottom_right).saturating_sub(1), i + get_y(upper_left)),
)) {
for row in grid
.bounds_iter(area.nth_row(i).take_cols(area.width().saturating_sub(1)))
{
for c in row {
grid[c]
.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 {
v.draw_cursor(
grid,
(
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
(get_x(bottom_right), i + get_y(upper_left)),
),
(
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
bottom_right,
),
area.nth_row(i).skip_cols(self.field_name_max_length + 3),
area.nth_row(i + 1)
.skip_cols(self.field_name_max_length + 3),
context,
);
}
@ -425,28 +404,13 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
let length = self.layout.len();
grid.clear_area(
(
pos_inc(upper_left, (0, length)),
set_y(bottom_right, length + 2 + get_y(upper_left)),
),
theme_default,
);
grid.clear_area(area.skip_rows(length).take_rows(length + 2), theme_default);
if !self.hide_buttons {
self.buttons.draw(
grid,
(
pos_inc(upper_left, (1, length + 3)),
set_y(bottom_right, length + 3 + get_y(upper_left)),
),
context,
);
self.buttons
.draw(grid, area.nth_row(length + 3).skip_cols(1), context);
}
if length + 4 < height!(area) {
grid.clear_area(
(pos_inc(upper_left, (0, length + 4)), bottom_right),
theme_default,
);
if length + 4 < area.height() {
grid.clear_area(area.skip_rows(length + 3), theme_default);
}
self.set_dirty(false);
context.dirty_areas.push_back(area);
@ -646,7 +610,6 @@ where
if self.dirty {
let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(area, theme_default);
let upper_left = upper_left!(area);
let mut len = 0;
for (i, k) in self.layout.iter().enumerate() {
@ -660,10 +623,7 @@ where
theme_default.bg
},
Attr::BOLD,
(
pos_inc(upper_left, (len, 0)),
pos_inc(upper_left, (cur_len + len, 0)),
),
area.skip_cols(len).take_cols(cur_len + len),
None,
);
len += cur_len + 3;
@ -755,7 +715,6 @@ impl From<(String, String)> for AutoCompleteEntry {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AutoComplete {
entries: Vec<AutoCompleteEntry>,
content: CellBuffer,
cursor: usize,
dirty: bool,
@ -775,8 +734,7 @@ impl Component for AutoComplete {
};
self.dirty = false;
let (upper_left, bottom_right) = area;
let rows = get_y(bottom_right) - get_y(upper_left);
let rows = area.height();
if rows == 0 {
return;
}
@ -784,43 +742,62 @@ impl Component for AutoComplete {
let top_idx = page_no * rows;
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"));
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 */
if self.cursor > 0 {
let highlight = crate::conf::value(context, "highlight");
grid.change_theme(
(
pos_inc(upper_left, (0, (self.cursor - 1) % rows)),
(
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))),
),
),
area.nth_row((self.cursor - 1) % rows)
.skip_cols(width.saturating_sub(1 + x_offset)),
highlight,
);
}
if rows < self.entries.len() {
ScrollBar { show_arrows: false }.draw(
grid,
(
set_y(pos_dec(bottom_right, (x_offset, 0)), get_y(upper_left)),
pos_dec(bottom_right, (x_offset, 0)),
),
area.take_rows(x_offset),
context,
self.cursor.saturating_sub(1),
rows,
@ -829,12 +806,15 @@ impl Component for AutoComplete {
}
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
false
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
}
@ -848,7 +828,6 @@ impl AutoComplete {
pub fn new(entries: Vec<AutoCompleteEntry>) -> Box<Self> {
let mut ret = AutoComplete {
entries: Vec::new(),
content: CellBuffer::default(),
cursor: 0,
dirty: true,
id: ComponentId::default(),
@ -862,45 +841,6 @@ impl AutoComplete {
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.cursor = 0;
true
@ -933,7 +873,6 @@ impl AutoComplete {
let ret = self.entries.remove(self.cursor - 1);
self.entries.clear();
self.cursor = 0;
self.content.empty();
Some(ret.entry)
}
@ -965,7 +904,7 @@ impl ScrollBar {
if length == 0 {
return;
}
let height = height!(area);
let height = area.height();
if height < 3 {
return;
}
@ -977,21 +916,21 @@ impl ScrollBar {
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_offset = (ratio * (pos as f64)) as usize;
let (mut upper_left, bottom_right) = area;
let mut area2 = area;
if self.show_arrows {
grid[upper_left]
grid[area2.upper_left()]
.set_ch(if ascii_drawing { '^' } else { '▀' })
.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 {
if get_y(upper_left) >= get_y(bottom_right) {
if area2.is_empty() {
break;
}
grid[upper_left]
grid[area2.upper_left()]
.set_ch(if ascii_drawing { '#' } else { '█' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_attrs(if !context.settings.terminal.use_color() {
@ -999,10 +938,10 @@ impl ScrollBar {
} else {
theme_default.attrs
});
upper_left = pos_inc(upper_left, (0, 1));
area2 = area2.skip_rows(1);
}
if self.show_arrows {
grid[bottom_right]
grid[area2.bottom_right()]
.set_ch(if ascii_drawing { 'v' } else { '▄' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg);
@ -1021,7 +960,7 @@ impl ScrollBar {
if length == 0 {
return;
}
let width = width!(area);
let width = area.width();
if width < 3 {
return;
}
@ -1033,21 +972,21 @@ impl ScrollBar {
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_offset = (ratio * (pos as f64)) as usize;
let (mut upper_left, bottom_right) = area;
let mut area2 = area;
if self.show_arrows {
grid[upper_left]
grid[area2.upper_left()]
.set_ch(if ascii_drawing { '<' } else { '▐' })
.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 {
if get_x(upper_left) >= get_x(bottom_right) {
if area2.is_empty() {
break;
}
grid[upper_left]
grid[area2.upper_left()]
.set_ch(if ascii_drawing { '#' } else { '█' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_attrs(if !context.settings.terminal.use_color() {
@ -1055,10 +994,10 @@ impl ScrollBar {
} else {
theme_default.attrs
});
upper_left = pos_inc(upper_left, (1, 0));
area2 = area2.skip_cols(1);
}
if self.show_arrows {
grid[bottom_right]
grid[area2.bottom_right()]
.set_ch(if ascii_drawing { '>' } else { '▌' })
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg);

View File

@ -127,7 +127,7 @@ impl Component for EmbedContainer {
embed_area,
((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);
self.dirty = false;
return;
@ -160,12 +160,12 @@ impl Component for EmbedContainer {
let inner_area = create_box(
grid,
(
pos_inc(upper_left!(area), (1, 0)),
pos_inc(area.upper_left(), (1, 0)),
pos_inc(
upper_left!(area),
area.upper_left(),
(
std::cmp::min(max_len + 5, width!(area)),
std::cmp::min(5, height!(area)),
std::cmp::min(max_len + 5, area.width()),
std::cmp::min(5, area.height()),
),
),
),
@ -185,10 +185,10 @@ impl Component for EmbedContainer {
theme_default.bg,
theme_default.attrs,
(
pos_inc((0, i), upper_left!(inner_area)),
bottom_right!(inner_area),
pos_inc((0, i), inner_area.upper_left()),
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 {
let theme_default = crate::conf::value(context, "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(
width!(self.embed_area),
height!(self.embed_area),
self.embed_area.width(),
self.embed_area.height(),
self.command.clone(),
) {
Ok(embed) => {