mail/composer: add scrollbars

jmap-eventsource
Manos Pitsidianakis 2020-10-16 12:35:51 +03:00
parent 1e7b40e6b3
commit 3949cecb75
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
8 changed files with 206 additions and 97 deletions

View File

@ -642,7 +642,7 @@ impl Component for ContactList {
let mut draft: Draft = Draft::default();
*draft.headers_mut().get_mut("To").unwrap() =
format!("{} <{}>", &card.name(), &card.email());
let mut composer = Composer::new(account_hash, context);
let mut composer = Composer::with_account(account_hash, context);
composer.set_draft(draft);
context
.replies

View File

@ -98,33 +98,6 @@ pub struct Composer {
id: ComponentId,
}
impl Default for Composer {
fn default() -> Self {
let mut pager = Pager::default();
pager.set_reflow(text_processing::Reflow::FormatFlowed);
Composer {
reply_context: None,
account_hash: 0,
cursor: Cursor::Headers,
pager,
draft: Draft::default(),
form: FormWidget::default(),
mode: ViewMode::Edit,
#[cfg(feature = "gpgme")]
gpg_state: gpg::GpgComposeState::new(),
dirty: true,
has_changes: false,
embed_area: ((0, 0), (0, 0)),
embed: None,
initialized: false,
id: ComponentId::new_v4(),
}
}
}
#[derive(Debug)]
enum ViewMode {
Discard(Uuid, UIDialog<char>),
@ -174,11 +147,32 @@ impl fmt::Display for Composer {
impl Composer {
const DESCRIPTION: &'static str = "composing";
pub fn new(account_hash: AccountHash, context: &Context) -> Self {
pub fn new(context: &Context) -> Self {
let mut pager = Pager::new(context);
pager.set_show_scrollbar(true);
Composer {
reply_context: None,
account_hash: 0,
cursor: Cursor::Headers,
pager,
draft: Draft::default(),
form: FormWidget::default(),
mode: ViewMode::Edit,
#[cfg(feature = "gpgme")]
gpg_state: gpg::GpgComposeState::new(),
dirty: true,
has_changes: false,
embed_area: ((0, 0), (0, 0)),
embed: None,
initialized: false,
id: ComponentId::new_v4(),
}
}
pub fn with_account(account_hash: AccountHash, context: &Context) -> Self {
let mut ret = Composer {
account_hash,
id: ComponentId::new_v4(),
..Default::default()
..Composer::new(context)
};
for (h, v) in
account_settings!(context[account_hash].composing.default_header_values).iter()
@ -194,8 +188,6 @@ impl Composer {
format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")),
);
}
ret.pager
.set_colors(crate::conf::value(context, "theme_default"));
ret
}
@ -205,7 +197,7 @@ impl Composer {
bytes: &[u8],
context: &Context,
) -> Result<Self> {
let mut ret = Composer::default();
let mut ret = Composer::with_account(account_hash, context);
let envelope: EnvelopeRef = context.accounts[&account_hash].collection.get_env(env_hash);
ret.draft = Draft::edit(&envelope, bytes)?;
@ -220,7 +212,7 @@ impl Composer {
context: &mut Context,
reply_to_all: bool,
) -> Self {
let mut ret = Composer::new(coordinates.0, context);
let mut ret = Composer::with_account(coordinates.0, context);
let account = &context.accounts[&coordinates.0];
let envelope = account.collection.get_env(coordinates.2);
let subject = envelope.subject();
@ -676,8 +668,8 @@ impl Component for Composer {
);
let body_area = (
pos_inc(upper_left, (mid + 1, header_height + 1)),
pos_dec(bottom_right, (mid, 4 + attachments_no)),
pos_inc(upper_left, (mid, header_height + 1)),
pos_dec(bottom_right, (mid, 5 + attachments_no)),
);
let (x, y) = write_string_to_grid(
@ -763,13 +755,24 @@ impl Component for Composer {
self.embed_area = (upper_left!(header_area), bottom_right!(body_area));
}
if !self.mode.is_edit_attachments() {
self.pager.set_dirty(true);
if self.pager.size().0 > width!(body_area) {
self.pager.set_initialised(false);
}
self.pager.draw(grid, body_area, context);
}
match self.cursor {
Cursor::Headers => {
change_colors(
grid,
(
pos_dec(upper_left!(body_area), (1, 0)),
pos_dec(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
(1, 0),
),
),
theme_default.fg,
theme_default.bg,
@ -779,8 +782,11 @@ impl Component for Composer {
change_colors(
grid,
(
pos_dec(upper_left!(body_area), (1, 0)),
pos_dec(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
(1, 0),
),
),
theme_default.fg,
Color::Byte(237),
@ -789,11 +795,6 @@ impl Component for Composer {
Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {}
}
if !self.mode.is_edit_attachments() {
self.pager.set_dirty(true);
self.pager.draw(grid, body_area, context);
}
match self.mode {
ViewMode::Edit | ViewMode::Embed => {}
ViewMode::EditAttachments { ref mut widget } => {

View File

@ -1211,7 +1211,7 @@ impl Component for Listing {
if shortcut!(k == shortcuts[Listing::DESCRIPTION]["new_mail"]) =>
{
let account_hash = context.accounts[self.cursor_pos.0].hash();
let composer = Composer::new(account_hash, context);
let composer = Composer::with_account(account_hash, context);
context
.replies
.push_back(UIEvent::Action(Tab(New(Some(Box::new(composer))))));

View File

@ -2143,7 +2143,8 @@ impl Component for MailView {
{
if let Ok(mailto) = Mailto::try_from(list_post_addr) {
let draft: Draft = mailto.into();
let mut composer = Composer::new(self.coordinates.0, context);
let mut composer =
Composer::with_account(self.coordinates.0, context);
composer.set_draft(draft);
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(composer),

View File

@ -570,8 +570,7 @@ impl ThreadView {
self.highlight_line(grid, dest_area, src_area, idx);
if rows < visibles.len() {
ScrollBar::draw(
ScrollBar::default(),
ScrollBar::default().draw(
grid,
(
upper_left!(area),
@ -624,8 +623,7 @@ impl ThreadView {
self.highlight_line(grid, dest_area, src_area, entry_idx);
if rows < visibles.len() {
ScrollBar::draw(
ScrollBar::default(),
ScrollBar::default().draw(
grid,
(
upper_left!(area),

View File

@ -316,10 +316,7 @@ impl Component for StatusBar {
}
let hist_height = std::cmp::min(15, self.auto_complete.suggestions().len());
let hist_area = if height < self.auto_complete.suggestions().len() {
let mut scrollbar = ScrollBar::default();
scrollbar.set_show_arrows(false);
scrollbar.set_block_character(Some('▌'));
scrollbar.draw(
ScrollBar::default().set_show_arrows(false).draw(
grid,
(
(

View File

@ -37,6 +37,7 @@ pub struct Pager {
colors: ThemeAttribute,
initialised: bool,
show_scrollbar: bool,
content: CellBuffer,
text_lines: (usize, Vec<String>),
movement: Option<PageMovement>,
@ -51,6 +52,23 @@ impl fmt::Display for Pager {
impl Pager {
pub const DESCRIPTION: &'static str = "pager";
pub fn new(context: &Context) -> Self {
let mut ret = Pager::default();
ret.minimum_width = context.settings.pager.minimum_width;
ret.set_colors(crate::conf::value(context, "theme_default"))
.set_reflow(if context.settings.pager.split_long_lines {
Reflow::All
} else {
Reflow::No
});
ret
}
pub fn set_show_scrollbar(&mut self, new_val: bool) -> &mut Self {
self.show_scrollbar = new_val;
self
}
pub fn set_colors(&mut self, new_val: ThemeAttribute) -> &mut Self {
self.colors = new_val;
self
@ -61,6 +79,11 @@ impl Pager {
self
}
pub fn set_initialised(&mut self, new_val: bool) -> &mut Self {
self.initialised = new_val;
self
}
pub fn reflow(&self) -> Reflow {
self.reflow
}
@ -264,13 +287,13 @@ impl Component for Pager {
width = self.minimum_width;
}
let lines: &[String] = if self.text_lines.0 == width.saturating_sub(2) {
let lines: &[String] = if self.text_lines.0 == width.saturating_sub(4) {
&self.text_lines.1
} else {
let lines = self
.text
.split_lines_reflow(self.reflow, Some(width.saturating_sub(2)));
self.text_lines = (width.saturating_sub(2), lines);
.split_lines_reflow(self.reflow, Some(width.saturating_sub(4)));
self.text_lines = (width.saturating_sub(4), lines);
&self.text_lines.1
};
let height = lines.len() + 2;
@ -344,30 +367,36 @@ impl Component for Pager {
self.cursor.1 = self.cursor.1.saturating_sub(height * multiplier);
}
PageMovement::Down(amount) => {
if self.cursor.1 + amount < self.height {
if self.cursor.1 + amount + 1 < self.height {
self.cursor.1 += amount;
} else {
self.cursor.1 = self.height.saturating_sub(1);
}
}
PageMovement::PageDown(multiplier) => {
if self.cursor.1 + height * multiplier < self.height {
if self.cursor.1 + height * multiplier + 1 < self.height {
self.cursor.1 += height * multiplier;
} else if self.cursor.1 + height * multiplier > self.height {
self.cursor.1 = self.height - 1;
} else {
self.cursor.1 = (self.height / height) * height;
}
}
PageMovement::Right(multiplier) => {
let offset = width!(area) / 3;
if self.cursor.0 + offset * multiplier < self.content.size().0 {
self.cursor.0 += offset * multiplier;
PageMovement::Right(amount) => {
if self.cursor.0 + amount + 1 < self.width {
self.cursor.0 += amount;
} else {
self.cursor.0 = self.width.saturating_sub(1);
}
}
PageMovement::Left(multiplier) => {
let offset = width!(area) / 3;
self.cursor.0 = self.cursor.0.saturating_sub(offset * multiplier);
PageMovement::Left(amount) => {
self.cursor.0 = self.cursor.0.saturating_sub(amount);
}
PageMovement::Home => {
self.cursor.1 = 0;
}
PageMovement::End => {
self.cursor.1 = (self.height / height) * height;
self.cursor.1 = self.height.saturating_sub(1);
}
}
}
@ -397,7 +426,13 @@ impl Component for Pager {
clear_area(grid, area, crate::conf::value(context, "theme_default"));
let (width, height) = self.content.size();
let (cols, rows) = (width!(area), height!(area));
let (mut cols, mut rows) = (width!(area), height!(area));
if self.show_scrollbar && rows < height {
cols -= 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),
@ -417,6 +452,32 @@ impl Component for Pager {
),
),
);
if self.show_scrollbar && rows + 1 < height {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
set_x(upper_left!(area), get_x(bottom_right!(area))),
bottom_right!(area),
),
context,
self.cursor.1,
rows,
height,
);
}
if self.show_scrollbar && cols + 1 < width {
ScrollBar::default().set_show_arrows(true).draw_horizontal(
grid,
(
set_y(upper_left!(area), get_y(bottom_right!(area))),
bottom_right!(area),
),
context,
self.cursor.0,
cols,
width,
);
}
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {

View File

@ -980,19 +980,17 @@ impl AutoComplete {
#[derive(Default)]
pub struct ScrollBar {
show_arrows: bool,
block_character: Option<char>,
pub show_arrows: bool,
}
impl ScrollBar {
pub fn set_show_arrows(&mut self, flag: bool) {
self.show_arrows = flag;
}
pub fn set_block_character(&mut self, val: Option<char>) {
self.block_character = val;
pub fn set_show_arrows(&mut self, new_val: bool) -> &mut Self {
self.show_arrows = new_val;
self
}
pub fn draw(
self,
&mut self,
grid: &mut CellBuffer,
area: Area,
context: &Context,
@ -1007,17 +1005,17 @@ impl ScrollBar {
if height < 3 {
return;
}
if self.show_arrows {
height -= height;
}
clear_area(grid, area, crate::conf::value(context, "theme_default"));
let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32);
let scrollbar_height = std::cmp::max((visible_ratio * (height as f32)) as usize, 1);
if self.show_arrows {
height -= 3;
}
let scrollbar_offset = {
let temp = (((pos as f32) / (length as f32)) * (height as f32)) as usize;
if temp + scrollbar_height >= height {
height - scrollbar_height
if scrollbar_height + temp > height {
height.saturating_sub(scrollbar_height)
} else {
temp
}
@ -1025,24 +1023,77 @@ impl ScrollBar {
let (mut upper_left, bottom_right) = area;
if self.show_arrows {
grid[upper_left].set_ch('▴');
upper_left = (upper_left.0, upper_left.1 + 1);
grid[upper_left]
.set_ch('▄')
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (0, 1));
}
for y in get_y(upper_left)..(get_y(upper_left) + scrollbar_offset) {
grid[set_y(upper_left, y)].set_ch(' ');
}
for y in (get_y(upper_left) + scrollbar_offset)
..=(get_y(upper_left) + scrollbar_offset + scrollbar_height)
{
grid[set_y(upper_left, y)].set_ch(self.block_character.unwrap_or('█'));
}
for y in (get_y(upper_left) + scrollbar_offset + scrollbar_height + 1)..get_y(bottom_right)
{
grid[set_y(upper_left, y)].set_ch(' ');
upper_left = pos_inc(upper_left, (0, scrollbar_offset));
for _ in 0..=scrollbar_height {
grid[upper_left].set_bg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (0, 1));
}
if self.show_arrows {
grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾');
grid[pos_dec(bottom_right, (0, 1))]
.set_ch('▀')
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg);
}
}
pub fn draw_horizontal(
&mut self,
grid: &mut CellBuffer,
area: Area,
context: &Context,
pos: usize,
visible_cols: usize,
length: usize,
) {
if length == 0 {
return;
}
let mut width = width!(area);
if width < 3 {
return;
}
clear_area(grid, area, crate::conf::value(context, "theme_default"));
let visible_ratio: f32 = (std::cmp::min(visible_cols, length) as f32) / (length as f32);
let scrollbar_width = std::cmp::max((visible_ratio * (width as f32)) as usize, 1);
if self.show_arrows {
width -= 3;
}
let scrollbar_offset = {
let temp = (((pos as f32) / (length as f32)) * (width as f32)) as usize;
if scrollbar_width + temp > width {
width.saturating_sub(scrollbar_width)
} else {
temp
}
};
let (mut upper_left, bottom_right) = area;
if self.show_arrows {
grid[upper_left]
.set_ch('▐')
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (1, 0));
}
upper_left = pos_inc(upper_left, (scrollbar_offset, 0));
for _ in 0..=scrollbar_width {
grid[upper_left]
.set_ch('█')
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg);
upper_left = pos_inc(upper_left, (1, 0));
}
if self.show_arrows {
grid[pos_dec(bottom_right, (1, 0))]
.set_ch('▌')
.set_fg(crate::conf::value(context, "widgets.options.highlighted").bg)
.set_bg(crate::conf::value(context, "theme_default").bg);
}
}
}