diff --git a/meli/src/command/argcheck.rs b/meli/src/command/argcheck.rs index a935818f..4ea8d2a6 100644 --- a/meli/src/command/argcheck.rs +++ b/meli/src/command/argcheck.rs @@ -47,9 +47,9 @@ impl ArgCheck { } ); }; - 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 ArgCheck { MIN ) .into(), - })); + }); } *self = Self::BeforeArgument { so_far: 0, diff --git a/meli/src/contacts/editor.rs b/meli/src/contacts/editor.rs index 41edfe5b..6549e615 100644 --- a/meli/src/contacts/editor.rs +++ b/meli/src/contacts/editor.rs @@ -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; } diff --git a/meli/src/contacts/list.rs b/meli/src/contacts/list.rs index 285742aa..81f6e396 100644 --- a/meli/src/contacts/list.rs +++ b/meli/src/contacts/list.rs @@ -21,12 +21,12 @@ use std::cmp; -use melib::{backends::AccountHash, log, text_processing::TextProcessing, Card, CardId, Draft}; +use melib::{backends::AccountHash, text_processing::TextProcessing, Card, CardId, Draft}; use crate::{ - conf, contacts::editor::ContactManager, shortcut, terminal::*, Action::Tab, Component, - ComponentId, Composer, Context, DataColumns, PageMovement, ScrollContext, ScrollUpdate, - ShortcutMaps, Shortcuts, StatusEvent, TabAction, ThemeAttribute, UIEvent, UIMode, + conf, /* contacts::editor::ContactManager, */ shortcut, terminal::*, Action::Tab, + Component, ComponentId, Composer, Context, DataColumns, PageMovement, ScrollContext, + ScrollUpdate, ShortcutMaps, Shortcuts, StatusEvent, TabAction, ThemeAttribute, UIEvent, UIMode, }; #[derive(Debug, PartialEq, Eq)] @@ -66,7 +66,7 @@ pub struct ContactList { menu_visibility: bool, movement: Option, cmd_buf: String, - view: Option, + //view: Option, ratio: usize, // right/(container width) * 100 id: ComponentId, } @@ -104,7 +104,7 @@ impl ContactList { dirty: true, movement: None, cmd_buf: String::with_capacity(8), - view: None, + //view: None, ratio: 90, sidebar_divider: context.settings.listing.sidebar_divider, sidebar_divider_theme: conf::value(context, "mail.sidebar_divider"), @@ -121,6 +121,7 @@ impl ContactList { } fn initialize(&mut self, context: &mut Context) { + self.data_columns.clear(); let account = &context.accounts[self.account_pos]; let book = &account.address_book; self.length = book.len(); @@ -134,25 +135,34 @@ impl ContactList { for c in book.values() { /* name */ - min_width.0 = cmp::max(min_width.0, c.name().split_graphemes().len()); + let name = c.name().split_graphemes().len(); + if name > 0 { + min_width.0 = cmp::max(min_width.0, name + 1); + } /* email */ - min_width.1 = cmp::max(min_width.1, c.email().split_graphemes().len()); + let email = c.email().split_graphemes().len(); + if email > 0 { + min_width.1 = cmp::max(min_width.1, email + 1); + } /* url */ - min_width.2 = cmp::max(min_width.2, c.url().split_graphemes().len()); + let url = c.url().split_graphemes().len(); + if url > 0 { + min_width.2 = cmp::max(min_width.2, url + 1); + } } /* name column */ - self.data_columns.columns[0] = - CellBuffer::new_with_context(min_width.0, self.length, None, context); + _ = self.data_columns.columns[0].resize_with_context(min_width.0, self.length, context); /* email column */ - self.data_columns.columns[1] = - CellBuffer::new_with_context(min_width.1, self.length, None, context); + _ = self.data_columns.columns[1].resize_with_context(min_width.1, self.length, context); /* url column */ - self.data_columns.columns[2] = - CellBuffer::new_with_context(min_width.2, self.length, None, context); + _ = self.data_columns.columns[2].resize_with_context(min_width.2, self.length, context); /* source column */ - self.data_columns.columns[3] = - CellBuffer::new_with_context("external".len(), self.length, None, context); + _ = self.data_columns.columns[3].resize_with_context( + "external".len(), + self.length, + context, + ); let account = &context.accounts[self.account_pos]; let book = &account.address_book; @@ -161,59 +171,73 @@ impl ContactList { for (idx, c) in book_values.iter().enumerate() { self.id_positions.push(*c.id()); - self.data_columns.columns[0].write_string( - c.name(), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((0, idx), (min_width.0, idx)), - None, - ); + { + let area = self.data_columns.columns[0].area().nth_row(idx); + self.data_columns.columns[0].grid_mut().write_string( + c.name(), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + area, + None, + ) + }; - self.data_columns.columns[1].write_string( - c.email(), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((0, idx), (min_width.1, idx)), - None, - ); + { + let area = self.data_columns.columns[1].area().nth_row(idx); + self.data_columns.columns[1].grid_mut().write_string( + c.email(), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + area, + None, + ) + }; - self.data_columns.columns[2].write_string( - c.url(), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((0, idx), (min_width.2, idx)), - None, - ); + { + let area = self.data_columns.columns[2].area().nth_row(idx); + self.data_columns.columns[2].grid_mut().write_string( + c.url(), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + area, + None, + ) + }; - self.data_columns.columns[3].write_string( - if c.external_resource() { - "external" - } else { - "local" - }, - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((0, idx), (min_width.3, idx)), - None, - ); + { + let area = self.data_columns.columns[3].area().nth_row(idx); + self.data_columns.columns[3].grid_mut().write_string( + if c.external_resource() { + "external" + } else { + "local" + }, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + area, + None, + ) + }; } if self.length == 0 { let message = "Address book is empty.".to_string(); - self.data_columns.columns[0] = - CellBuffer::new_with_context(message.len(), self.length, None, context); - self.data_columns.columns[0].write_string( - &message, - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((0, 0), (message.len() - 1, 0)), - None, - ); + if self.data_columns.columns[0].resize_with_context(message.len(), self.length, context) + { + let area = self.data_columns.columns[0].area(); + self.data_columns.columns[0].grid_mut().write_string( + &message, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + area, + None, + ); + } } } @@ -236,17 +260,14 @@ impl ContactList { return; } grid.clear_area(area, self.theme_default); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); self.dirty = false; - let mut y = get_y(upper_left); - for a in &self.accounts { - self.print_account(grid, (set_y(upper_left, y), bottom_right), a, context); - y += 1; + for (y, a) in self.accounts.iter().enumerate() { + self.print_account(grid, area.nth_row(y), a, context); } context.dirty_areas.push_back(area); } + /* * Print a single account in the menu area. */ @@ -257,11 +278,7 @@ impl ContactList { a: &AccountMenuEntry, context: &mut Context, ) { - if !is_valid_area!(area) { - log::debug!("BUG: invalid area in print_account"); - } - - let width = width!(area); + let width = area.width(); let must_highlight_account: bool = self.account_pos == a.index; let account_attrs = if must_highlight_account { let mut v = crate::conf::value(context, "mail.sidebar_highlighted"); @@ -273,104 +290,53 @@ impl ContactList { crate::conf::value(context, "mail.sidebar_account_name") }; + grid.change_theme(area, account_attrs); let s = format!(" [{}]", context.accounts[a.index].address_book.len()); + /* Print account name */ + grid.write_string( + &a.name, + account_attrs.fg, + account_attrs.bg, + account_attrs.attrs, + area, + None, + ); + grid.write_string( + &s, + account_attrs.fg, + account_attrs.bg, + account_attrs.attrs, + area.skip_cols(area.width().saturating_sub(s.len())), + None, + ); if a.name.grapheme_len() + s.len() > width + 1 { - /* Print account name */ - let (x, y) = grid.write_string( - &a.name, - account_attrs.fg, - account_attrs.bg, - account_attrs.attrs, - area, - None, - ); - grid.write_string( - &s, - account_attrs.fg, - account_attrs.bg, - account_attrs.attrs, - ( - pos_dec( - (get_x(bottom_right!(area)), get_y(upper_left!(area))), - (s.len() - 1, 0), - ), - bottom_right!(area), - ), - None, - ); grid.write_string( "…", account_attrs.fg, account_attrs.bg, account_attrs.attrs, - ( - pos_dec( - (get_x(bottom_right!(area)), get_y(upper_left!(area))), - (s.len() - 1, 0), - ), - bottom_right!(area), - ), + area.skip_cols(area.width().saturating_sub(s.len() + 1)), None, ); - - for x in x..=get_x(bottom_right!(area)) { - grid[(x, y)] - .set_fg(account_attrs.fg) - .set_bg(account_attrs.bg) - .set_attrs(account_attrs.attrs); - } - } else { - /* Print account name */ - - let (x, y) = grid.write_string( - &a.name, - account_attrs.fg, - account_attrs.bg, - account_attrs.attrs, - area, - None, - ); - grid.write_string( - &s, - account_attrs.fg, - account_attrs.bg, - account_attrs.attrs, - ( - pos_dec( - (get_x(bottom_right!(area)), get_y(upper_left!(area))), - (s.len() - 1, 0), - ), - bottom_right!(area), - ), - None, - ); - for x in x..=get_x(bottom_right!(area)) { - grid[(x, y)] - .set_fg(account_attrs.fg) - .set_bg(account_attrs.bg) - .set_attrs(account_attrs.attrs); - } } } fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - /* reserve top row for column headers */ - let upper_left = pos_inc(upper_left!(area), (0, 1)); - let bottom_right = bottom_right!(area); - if self.length == 0 { + /* reserve top row for column headers */ + let area = area.skip_rows(1); grid.clear_area(area, self.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 let Some(mvm) = self.movement.take() { match mvm { @@ -440,12 +406,9 @@ impl ContactList { self.cursor_pos = self.new_cursor_pos; for idx in &[old_cursor_pos, self.new_cursor_pos] { if *idx >= self.length { - continue; //bounds check + continue; } - let new_area = ( - set_y(upper_left, get_y(upper_left) + (*idx % rows)), - set_y(bottom_right, get_y(upper_left) + (*idx % rows)), - ); + let new_area = area.nth_row(1 + *idx % rows); self.highlight_line(grid, new_area, *idx); context.dirty_areas.push_back(new_area); } @@ -458,33 +421,25 @@ impl ContactList { self.cursor_pos = self.new_cursor_pos; } - let width = width!(area); - self.data_columns.widths = Default::default(); - self.data_columns.widths[0] = self.data_columns.columns[0].size().0; /* name */ - self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email */ - self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* url */ - self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* source */ - - let min_col_width = std::cmp::min( - 15, - std::cmp::min(self.data_columns.widths[0], self.data_columns.widths[1]), - ); - if self.data_columns.widths[0] + self.data_columns.widths[1] + 3 * min_col_width + 8 > width - { - let remainder = - width.saturating_sub(self.data_columns.widths[0] + self.data_columns.widths[1] + 4); - self.data_columns.widths[2] = remainder / 6; - } - grid.clear_area(area, self.theme_default); /* Page_no has changed, so draw new page */ + grid.clear_area(area, self.theme_default); + _ = self + .data_columns + .recalc_widths((area.width(), area.height().saturating_sub(1)), top_idx); + /* copy table columns */ + self.data_columns.draw( + grid, + top_idx, + self.cursor_pos, + grid.bounds_iter(area.skip_rows(1)), + ); let header_attrs = crate::conf::value(context, "widgets.list.header"); - let mut x = get_x(upper_left); + let mut x = 0; for i in 0..self.data_columns.columns.len() { if self.data_columns.widths[i] == 0 { continue; } - let (column_width, column_height) = self.data_columns.columns[i].size(); grid.write_string( match i { 0 => "NAME", @@ -496,62 +451,28 @@ impl ContactList { header_attrs.fg, header_attrs.bg, header_attrs.attrs, - ( - set_x(upper_left!(area), x), - ( - std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])), - get_y(upper_left!(area)), - ), - ), + area.skip_cols(x) + .take_cols(x + (self.data_columns.widths[i])), None, ); - grid.copy_area( - &self.data_columns.columns[i], - ( - set_x(upper_left, x), - set_x( - bottom_right, - std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])), - ), - ), - ( - (0, top_idx), - ( - column_width.saturating_sub(1), - column_height.saturating_sub(1), - ), - ), - ); x += self.data_columns.widths[i] + 2; // + SEPARATOR - if x > get_x(bottom_right) { + if x > area.width() { break; } } - grid.change_theme( - ( - upper_left!(area), - set_y(bottom_right, get_y(upper_left!(area))), - ), - header_attrs, - ); + grid.change_theme(area.nth_row(0), header_attrs); if top_idx + rows > self.length { grid.clear_area( - ( - pos_inc(upper_left, (0, self.length - top_idx + 2)), - bottom_right, - ), + area.skip_rows(top_idx + rows - self.length.saturating_sub(1)), self.theme_default, ); } self.highlight_line( grid, - ( - set_y(upper_left, get_y(upper_left) + (self.cursor_pos % rows)), - set_y(bottom_right, get_y(upper_left) + (self.cursor_pos % rows)), - ), + area.nth_row(1 + self.cursor_pos % rows), self.cursor_pos, ); context.dirty_areas.push_back(area); @@ -560,10 +481,10 @@ impl ContactList { impl Component for ContactList { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if let Some(mgr) = self.view.as_mut() { - mgr.draw(grid, area, context); - return; - } + //if let Some(mgr) = self.view.as_mut() { + // mgr.draw(grid, area, context); + // return; + //} if !self.dirty { return; @@ -572,27 +493,26 @@ impl Component for ContactList { self.initialize(context); } - 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.menu_visibility { (self.ratio * total_cols) / 100 } 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); } if right_component_width == total_cols { @@ -600,18 +520,19 @@ impl Component for ContactList { } 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_list(grid, (set_x(upper_left, mid + 1), bottom_right), context); + self.draw_menu(grid, area.take_cols(mid), context); + self.draw_list(grid, area.skip_cols(mid + 1), context); } self.dirty = false; } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { match event { + UIEvent::VisibilityChange(true) => { + self.initialized = false; + self.set_dirty(true); + return true; + } UIEvent::ConfigReload { old_settings: _ } => { self.theme_default = crate::conf::value(context, "theme_default"); self.initialized = false; @@ -625,7 +546,7 @@ impl Component for ContactList { } UIEvent::ComponentUnrealize(ref kill_id) if self.mode == ViewMode::View(*kill_id) => { self.mode = ViewMode::List; - self.view.take(); + //self.view.take(); self.set_dirty(true); return true; } @@ -638,313 +559,314 @@ impl Component for ContactList { _ => {} } - if let Some(ref mut v) = self.view { - if v.process_event(event, context) { - return true; - } - } + //if let Some(ref mut v) = self.view { + // if v.process_event(event, context) { + // return true; + // } + //} let shortcuts = self.shortcuts(context); - if self.view.is_none() { - match *event { - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["create_contact"]) => - { - let mut manager = ContactManager::new(context); - manager.set_parent_id(self.id); - manager.account_pos = self.account_pos; + //if self.view.is_none() { + match *event { + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["create_contact"]) => + { + /* + let mut manager = ContactManager::new(context); + manager.set_parent_id(self.id); + manager.account_pos = self.account_pos; - self.mode = ViewMode::View(manager.id()); - self.view = Some(manager); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::End(self.id), - ))); + self.mode = ViewMode::View(manager.id()); + self.view = Some(manager); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::End(self.id), + ))); + */ + return true; + } + + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["edit_contact"]) => + { + if self.length == 0 { return true; } + /* + let account = &mut context.accounts[self.account_pos]; + let book = &mut account.address_book; + let card = book[&self.id_positions[self.cursor_pos]].clone(); + let mut manager = ContactManager::new(context); + manager.set_parent_id(self.id); + manager.card = card; + manager.account_pos = self.account_pos; - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["edit_contact"]) => - { - if self.length == 0 { - return true; - } - let account = &mut context.accounts[self.account_pos]; - let book = &mut account.address_book; - let card = book[&self.id_positions[self.cursor_pos]].clone(); - let mut manager = ContactManager::new(context); - manager.set_parent_id(self.id); - manager.card = card; - manager.account_pos = self.account_pos; - - self.mode = ViewMode::View(manager.id()); - self.view = Some(manager); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::End(self.id), - ))); + self.mode = ViewMode::View(manager.id()); + self.view = Some(manager); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::End(self.id), + ))); + */ + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["mail_contact"]) => + { + if self.length == 0 { return true; } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["mail_contact"]) => - { - if self.length == 0 { - return true; - } - let account = &context.accounts[self.account_pos]; - let account_hash = account.hash(); - let book = &account.address_book; - let card = &book[&self.id_positions[self.cursor_pos]]; - let mut draft: Draft = Draft::default(); - *draft.headers_mut().get_mut("To").unwrap() = - format!("{} <{}>", &card.name(), &card.email()); - let mut composer = Composer::with_account(account_hash, context); - composer.set_draft(draft, context); - context - .replies - .push_back(UIEvent::Action(Tab(TabAction::New(Some(Box::new( - composer, - )))))); + let account = &context.accounts[self.account_pos]; + let account_hash = account.hash(); + let book = &account.address_book; + let card = &book[&self.id_positions[self.cursor_pos]]; + let mut draft: Draft = Draft::default(); + *draft.headers_mut().get_mut("To").unwrap() = + format!("{} <{}>", &card.name(), &card.email()); + let mut composer = Composer::with_account(account_hash, context); + composer.set_draft(draft, context); + context + .replies + .push_back(UIEvent::Action(Tab(TabAction::New(Some(Box::new( + composer, + )))))); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["delete_contact"]) => + { + if self.length == 0 { return true; } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["delete_contact"]) => - { - if self.length == 0 { - return true; - } - // [ref:TODO]: add a confirmation dialog? - context.accounts[self.account_pos] - .address_book - .remove_card(self.id_positions[self.cursor_pos]); - self.initialized = false; - self.set_dirty(true); + // [ref:TODO]: add a confirmation dialog? + context.accounts[self.account_pos] + .address_book + .remove_card(self.id_positions[self.cursor_pos]); + self.initialized = false; + self.set_dirty(true); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["next_account"]) => + { + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); context .replies .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["next_account"]) => - { - let amount = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(amount) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - amount - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - if self.account_pos + amount < self.accounts.len() { - self.account_pos += amount; - self.set_dirty(true); - self.initialized = false; - self.cursor_pos = 0; - self.new_cursor_pos = 0; - self.length = 0; - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( - self.status(context), - ))); - } - - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["prev_account"]) => - { - let amount = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(amount) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - amount - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - if self.accounts.is_empty() { - return true; - } - if self.account_pos >= amount { - self.account_pos -= amount; - self.set_dirty(true); - self.cursor_pos = 0; - self.new_cursor_pos = 0; - self.length = 0; - self.initialized = false; - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( - self.status(context), - ))); - } - return true; - } - UIEvent::Input(ref k) - if shortcut!( - k == shortcuts[Shortcuts::CONTACT_LIST]["toggle_menu_visibility"] - ) => - { - self.menu_visibility = !self.menu_visibility; - self.set_dirty(true); - } - UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) - if !self.cmd_buf.is_empty() => - { + amount + } else { self.cmd_buf.clear(); context .replies .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); return true; - } - UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => { - self.cmd_buf.push(c); + }; + if self.account_pos + amount < self.accounts.len() { + self.account_pos += amount; + self.set_dirty(true); + self.initialized = false; + self.cursor_pos = 0; + self.new_cursor_pos = 0; + self.length = 0; context .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufSet( - self.cmd_buf.clone(), + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( + self.status(context), ))); - return true; } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_up"]) => - { - let amount = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(amount) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - amount - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - self.movement = Some(PageMovement::Up(amount)); - self.set_dirty(true); - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_down"]) => - { - if self.cursor_pos >= self.length.saturating_sub(1) { - return true; - } - let amount = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(amount) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - amount - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - self.set_dirty(true); - self.movement = Some(PageMovement::Down(amount)); - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) => - { - let mult = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(mult) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - mult - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - self.set_dirty(true); - self.movement = Some(PageMovement::PageUp(mult)); - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) => - { - let mult = if self.cmd_buf.is_empty() { - 1 - } else if let Ok(mult) = self.cmd_buf.parse::() { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - mult - } else { - self.cmd_buf.clear(); - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); - return true; - }; - self.set_dirty(true); - self.movement = Some(PageMovement::PageDown(mult)); - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) => - { - self.set_dirty(true); - self.movement = Some(PageMovement::Home); - return true; - } - UIEvent::Input(ref key) - if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) => - { - self.set_dirty(true); - self.movement = Some(PageMovement::End); - return true; - } - _ => {} + + return true; } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["prev_account"]) => + { + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + amount + } else { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + if self.accounts.is_empty() { + return true; + } + if self.account_pos >= amount { + self.account_pos -= amount; + self.set_dirty(true); + self.cursor_pos = 0; + self.new_cursor_pos = 0; + self.length = 0; + self.initialized = false; + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( + self.status(context), + ))); + } + return true; + } + UIEvent::Input(ref k) + if shortcut!(k == shortcuts[Shortcuts::CONTACT_LIST]["toggle_menu_visibility"]) => + { + self.menu_visibility = !self.menu_visibility; + self.set_dirty(true); + } + UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + } + UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => { + self.cmd_buf.push(c); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufSet( + self.cmd_buf.clone(), + ))); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_up"]) => + { + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + amount + } else { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.movement = Some(PageMovement::Up(amount)); + self.set_dirty(true); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_down"]) => + { + if self.cursor_pos >= self.length.saturating_sub(1) { + return true; + } + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + amount + } else { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.set_dirty(true); + self.movement = Some(PageMovement::Down(amount)); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) => + { + let mult = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(mult) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + mult + } else { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.set_dirty(true); + self.movement = Some(PageMovement::PageUp(mult)); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) => + { + let mult = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(mult) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + mult + } else { + self.cmd_buf.clear(); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.set_dirty(true); + self.movement = Some(PageMovement::PageDown(mult)); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) => + { + self.set_dirty(true); + self.movement = Some(PageMovement::Home); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) => + { + self.set_dirty(true); + self.movement = Some(PageMovement::End); + return true; + } + _ => {} } + //} false } fn is_dirty(&self) -> bool { - self.dirty || self.view.as_ref().map(|v| v.is_dirty()).unwrap_or(false) + self.dirty //|| self.view.as_ref().map(|v| + //|| v.is_dirty()).unwrap_or(false) } fn set_dirty(&mut self, value: bool) { - if let Some(p) = self.view.as_mut() { - p.set_dirty(value); - }; + //if let Some(p) = self.view.as_mut() { + // p.set_dirty(value); + //}; self.dirty = value; } @@ -955,11 +877,11 @@ impl Component for ContactList { .push_back(UIEvent::Action(Tab(TabAction::Kill(uuid)))); } fn shortcuts(&self, context: &Context) -> ShortcutMaps { - let mut map = self - .view - .as_ref() - .map(|p| p.shortcuts(context)) - .unwrap_or_default(); + let mut map = ShortcutMaps::default(); //self + //.view + //.as_ref() + //.map(|p| p.shortcuts(context)) + //.unwrap_or_default(); map.insert( Shortcuts::CONTACT_LIST, @@ -977,11 +899,10 @@ impl Component for ContactList { self.id } - fn can_quit_cleanly(&mut self, context: &Context) -> bool { - self.view - .as_mut() - .map(|p| p.can_quit_cleanly(context)) - .unwrap_or(true) + fn can_quit_cleanly(&mut self, _context: &Context) -> bool { + true + //self.view .as_mut() .map(|p| p.can_quit_cleanly(context)) + // .unwrap_or(true) } fn status(&self, context: &Context) -> String { diff --git a/meli/src/contacts/mod.rs b/meli/src/contacts/mod.rs index c063b1c5..79e33e84 100644 --- a/meli/src/contacts/mod.rs +++ b/meli/src/contacts/mod.rs @@ -21,4 +21,4 @@ pub mod list; -pub mod editor; +//pub mod editor; diff --git a/meli/src/jobs_view.rs b/meli/src/jobs_view.rs index b52767fc..50aaabc5 100644 --- a/meli/src/jobs_view.rs +++ b/meli/src/jobs_view.rs @@ -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; diff --git a/meli/src/lib.rs b/meli/src/lib.rs index 5fb3cd1d..6c816669 100644 --- a/meli/src/lib.rs +++ b/meli/src/lib.rs @@ -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; diff --git a/meli/src/mail/compose.rs b/meli/src/mail/compose.rs index a481a830..6d0cc5c9 100644 --- a/meli/src/mail/compose.rs +++ b/meli/src/mail/compose.rs @@ -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, + embedded: Option, + 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), - EditAttachments { - widget: EditAttachments, - }, + //EditAttachments { + // widget: EditAttachments, + //}, Edit, Embed, SelectRecipients(UIDialog
), @@ -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) { diff --git a/meli/src/mail/compose/edit_attachments.rs b/meli/src/mail/compose/edit_attachments.rs index 53b41d08..378695cb 100644 --- a/meli/src/mail/compose/edit_attachments.rs +++ b/meli/src/mail/compose/edit_attachments.rs @@ -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, ); diff --git a/meli/src/mail/compose/gpg.rs b/meli/src/mail/compose/gpg.rs index 589f9919..17127e57 100644 --- a/meli/src/mail/compose/gpg.rs +++ b/meli/src/mail/compose/gpg.rs @@ -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), ); } diff --git a/meli/src/mail/listing.rs b/meli/src/mail/listing.rs index f8f7e336..9b6af4be 100644 --- a/meli/src/mail/listing.rs +++ b/meli/src/mail/listing.rs @@ -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 RowsState { } } -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), - Threaded(Box), - Compact(Box), - Conversations(Box), + //Compact(Box), + //Conversations(Box), Offline(Box), + Plain(Box), + //Threaded(Box), } 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, 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 = 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::::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::(); - 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 = 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); } } diff --git a/meli/src/mail/listing/compact.rs b/meli/src/mail/listing/compact.rs index 1d9fb6b5..8da65604 100644 --- a/meli/src/mail/listing/compact.rs +++ b/meli/src/mail/listing/compact.rs @@ -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() { diff --git a/meli/src/mail/listing/conversations.rs b/meli/src/mail/listing/conversations.rs index 1c8b7f3b..7f560002 100644 --- a/meli/src/mail/listing/conversations.rs +++ b/meli/src/mail/listing/conversations.rs @@ -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); diff --git a/meli/src/mail/listing/offline.rs b/meli/src/mail/listing/offline.rs index 2494cfaf..d8c1b06c 100644 --- a/meli/src/mail/listing/offline.rs +++ b/meli/src/mail/listing/offline.rs @@ -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 diff --git a/meli/src/mail/listing/plain.rs b/meli/src/mail/listing/plain.rs index 1a0cb84b..f52634e7 100644 --- a/meli/src/mail/listing/plain.rs +++ b/meli/src/mail/listing/plain.rs @@ -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) { diff --git a/meli/src/mail/listing/thread.rs b/meli/src/mail/listing/thread.rs index 35df8800..0337d8ab 100644 --- a/meli/src/mail/listing/thread.rs +++ b/meli/src/mail/listing/thread.rs @@ -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); diff --git a/meli/src/mail/status.rs b/meli/src/mail/status.rs index 6450a8f0..754eb8d5 100644 --- a/meli/src/mail/status.rs +++ b/meli/src/mail/status.rs @@ -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, 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::::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); } diff --git a/meli/src/mail/view/envelope.rs b/meli/src/mail/view/envelope.rs index 36ea8e81..9af9c489 100644 --- a/meli/src/mail/view/envelope.rs +++ b/meli/src/mail/view/envelope.rs @@ -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); diff --git a/meli/src/mail/view/thread.rs b/meli/src/mail/view/thread.rs index 94ab6646..7a8d4717 100644 --- a/meli/src/mail/view/thread.rs +++ b/meli/src/mail/view/thread.rs @@ -65,7 +65,7 @@ pub struct ThreadView { horizontal: Option, movement: Option, dirty: bool, - content: CellBuffer, + content: Screen, 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, diff --git a/meli/src/mailbox_management.rs b/meli/src/mailbox_management.rs index 5b30fbcb..93fbaf40 100644 --- a/meli/src/mailbox_management.rs +++ b/meli/src/mailbox_management.rs @@ -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 { diff --git a/meli/src/state.rs b/meli/src/state.rs index 33143e30..bd541424 100644 --- a/meli/src/state.rs +++ b/meli/src/state.rs @@ -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: Box>, draw_rate_limit: RateLimit, child: Option, 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::::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| { diff --git a/meli/src/svg.rs b/meli/src/svg.rs index 9bcea757..e7df5d8d 100644 --- a/meli/src/svg.rs +++ b/meli/src/svg.rs @@ -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 group element, consisting of text elements */ diff --git a/meli/src/terminal.rs b/meli/src/terminal.rs index 7ab57dab..7203318d 100644 --- a/meli/src/terminal.rs +++ b/meli/src/terminal.rs @@ -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::*}; diff --git a/meli/src/terminal/cells.rs b/meli/src/terminal/cells.rs index 113c48e4..13562556 100644 --- a/meli/src/terminal/cells.rs +++ b/meli/src/terminal/cells.rs @@ -35,7 +35,7 @@ use melib::{ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use smallvec::SmallVec; -use super::{position::*, Color}; +use super::{position::*, Area, Color, ScreenGeneration}; use crate::{state::Context, ThemeAttribute}; /// In a scroll region up and down cursor movements shift the region vertically. @@ -73,6 +73,7 @@ pub struct CellBuffer { growable: bool, tag_table: HashMap, tag_associations: SmallVec<[(u64, (usize, usize)); 128]>, + pub(super) area: Area, } impl std::fmt::Debug for CellBuffer { @@ -87,25 +88,39 @@ impl std::fmt::Debug for CellBuffer { .field("growable", &self.growable) .field("tag_table", &self.tag_table) .field("tag_associations", &self.tag_associations) + .field("area", &self.area) + .field("generation", &self.area.generation()) .finish() } } impl CellBuffer { - pub const MAX_SIZE: usize = 300_000; - pub fn area(&self) -> Area { - ( - (0, 0), - (self.cols.saturating_sub(1), self.rows.saturating_sub(1)), - ) + pub const MAX_SIZE: usize = 1_000_000; + + pub fn nil(area: Area) -> Self { + Self { + cols: 0, + rows: 0, + buf: vec![], + default_cell: Cell::new_default(), + growable: false, + ascii_drawing: false, + use_color: false, + tag_table: Default::default(), + tag_associations: SmallVec::new(), + area, + } } + pub fn set_cols(&mut self, new_cols: usize) { self.cols = new_cols; } /// Constructs a new `CellBuffer` with the given number of columns and rows, /// using the given `cell` as a blank. - pub fn new(cols: usize, rows: usize, default_cell: Cell) -> Self { + pub fn new(default_cell: Cell, area: Area) -> Self { + let cols = area.width(); + let rows = area.height(); Self { cols, rows, @@ -116,15 +131,11 @@ impl CellBuffer { use_color: true, tag_table: Default::default(), tag_associations: SmallVec::new(), + area, } } - pub fn new_with_context( - cols: usize, - rows: usize, - default_cell: Option, - context: &Context, - ) -> Self { + pub fn new_with_context(default_cell: Option, area: Area, context: &Context) -> Self { let default_cell = default_cell.unwrap_or_else(|| { let mut ret = Cell::default(); let theme_default = crate::conf::value(context, "theme_default"); @@ -134,15 +145,9 @@ impl CellBuffer { ret }); Self { - cols, - rows, - buf: vec![default_cell; cols * rows], - default_cell, - growable: false, ascii_drawing: context.settings.terminal.ascii_drawing, use_color: context.settings.terminal.use_color(), - tag_table: Default::default(), - tag_associations: SmallVec::new(), + ..Self::new(default_cell, area) } } @@ -161,21 +166,45 @@ impl CellBuffer { /// Resizes `CellBuffer` to the given number of rows and columns, using the /// given `Cell` as a blank. #[must_use] - pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Option) -> bool { + pub(super) fn resize_with_context( + &mut self, + newcols: usize, + newrows: usize, + context: &Context, + ) -> bool { + self.default_cell = { + let mut ret = Cell::default(); + let theme_default = crate::conf::value(context, "theme_default"); + ret.set_fg(theme_default.fg) + .set_bg(theme_default.bg) + .set_attrs(theme_default.attrs); + ret + }; + self.ascii_drawing = context.settings.terminal.ascii_drawing; + self.use_color = context.settings.terminal.use_color(); + + let newlen = newcols * newrows; + if (self.cols, self.rows) == (newcols, newrows) || newlen >= Self::MAX_SIZE { + return newlen < Self::MAX_SIZE; + } + + self.buf = vec![self.default_cell; newlen]; + self.cols = newcols; + self.rows = newrows; + true + } + + /// Resizes `CellBuffer` to the given number of rows and columns, using the + /// given `Cell` as a blank. + #[must_use] + pub(super) fn resize(&mut self, newcols: usize, newrows: usize, blank: Option) -> bool { let newlen = newcols * newrows; if (self.cols, self.rows) == (newcols, newrows) || newlen >= Self::MAX_SIZE { return newlen < Self::MAX_SIZE; } let blank = blank.unwrap_or(self.default_cell); - let mut newbuf: Vec = Vec::with_capacity(newlen); - for y in 0..newrows { - for x in 0..newcols { - let cell = self.get(x, y).unwrap_or(&blank); - newbuf.push(*cell); - } - } - self.buf = newbuf; + self.buf = vec![blank; newlen]; self.cols = newcols; self.rows = newrows; true @@ -359,33 +388,44 @@ impl CellBuffer { /// See `BoundsIterator` documentation. pub fn bounds_iter(&self, area: Area) -> BoundsIterator { + debug_assert_eq!(self.generation(), area.generation()); + BoundsIterator { - width: width!(area), - height: height!(area), - rows: std::cmp::min(self.rows.saturating_sub(1), get_y(upper_left!(area))) - ..(std::cmp::min(self.rows, get_y(bottom_right!(area)) + 1)), + width: area.width(), + height: area.height(), + rows: std::cmp::min(self.rows.saturating_sub(1), get_y(area.upper_left())) + ..(std::cmp::min(self.rows, get_y(area.bottom_right()) + 1)), cols: ( - std::cmp::min(self.cols.saturating_sub(1), get_x(upper_left!(area))), - std::cmp::min(self.cols, get_x(bottom_right!(area)) + 1), + std::cmp::min(self.cols.saturating_sub(1), get_x(area.upper_left())), + std::cmp::min(self.cols, get_x(area.bottom_right()) + 1), ), + area, } } /// See `RowIterator` documentation. - pub fn row_iter(&self, bounds: std::ops::Range, row: usize) -> RowIterator { + pub fn row_iter( + &self, + area: Area, + bounds: std::ops::Range, + relative_row: usize, + ) -> RowIterator { + debug_assert_eq!(self.generation(), area.generation()); + if self.generation() != area.generation() { + return RowIterator::empty(self.generation()); + } + let row = area.offset().1 + relative_row; + if row < self.rows { - RowIterator { - row, - col: std::cmp::min(self.cols.saturating_sub(1), bounds.start) - ..(std::cmp::min(self.cols, bounds.end)), - _width: bounds.len(), - } + let col = std::cmp::min(self.cols.saturating_sub(1), area.offset().0 + bounds.start) + ..(std::cmp::min(self.cols, area.offset().0 + bounds.end)); + let area = area + .nth_row(relative_row) + .skip_cols(bounds.start) + .take_cols(bounds.len()); + RowIterator { row, col, area } } else { - RowIterator { - row, - col: 0..0, - _width: 0, - } + RowIterator::empty(self.generation()) } } @@ -435,207 +475,98 @@ impl CellBuffer { } } - /// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed - /// colors. - pub fn write_string( - &mut self, - s: &str, - fg_color: Color, - bg_color: Color, - attrs: Attr, - area: Area, - // The left-most x coordinate. - line_break: Option, - ) -> Pos { - macro_rules! inspect_bounds { - ($grid:ident, $area:ident, $x: ident, $y: ident, $line_break:ident) => { - let bounds = $grid.size(); - let (upper_left, bottom_right) = $area; - if $x > (get_x(bottom_right)) || $x >= get_x(bounds) { - if $grid.growable { - if !$grid.resize(std::cmp::max($x + 1, $grid.cols), $grid.rows, None) { - break; - }; - } else { - $x = get_x(upper_left); - $y += 1; - if let Some(_x) = $line_break { - $x = _x; - } else { - break; - } - } - } - if $y > (get_y(bottom_right)) || $y >= get_y(bounds) { - if $grid.growable { - if !$grid.resize($grid.cols, std::cmp::max($y + 1, $grid.rows), None) { - break; - }; - } else { - return ($x, $y - 1); - } - } - }; - } - - let mut bounds = self.size(); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let (mut x, mut y) = upper_left; - if y == get_y(bounds) || x == get_x(bounds) { - if self.growable { - if !self.resize( - std::cmp::max(self.cols, x + 2), - std::cmp::max(self.rows, y + 2), - None, - ) { - return (x, y); - } - bounds = self.size(); - } else { - return (x, y); - } - } - - if y > (get_y(bottom_right)) - || x > get_x(bottom_right) - || y > get_y(bounds) - || x > get_x(bounds) - { - if self.growable { - if !self.resize( - std::cmp::max(self.cols, x + 2), - std::cmp::max(self.rows, y + 2), - None, - ) { - return (x, y); - } - } else { - log::debug!(" Invalid area with string {} and area {:?}", s, area); - return (x, y); - } - } - for c in s.chars() { - inspect_bounds!(self, area, x, y, line_break); - if c == '\r' { - continue; - } - if c == '\n' { - y += 1; - if let Some(_x) = line_break { - x = _x; - inspect_bounds!(self, area, x, y, line_break); - continue; - } else { - break; - } - } - if c == '\t' { - self[(x, y)].set_ch(' '); - x += 1; - inspect_bounds!(self, area, x, y, line_break); - self[(x, y)].set_ch(' '); - } else { - self[(x, y)].set_ch(c); - } - self[(x, y)] - .set_fg(fg_color) - .set_bg(bg_color) - .set_attrs(attrs); - - match wcwidth(u32::from(c)) { - Some(0) | None => { - /* Skip drawing zero width characters */ - self[(x, y)].empty = true; - } - Some(2) => { - /* Grapheme takes more than one column, so the next cell will be - * drawn over. Set it as empty to skip drawing it. */ - x += 1; - inspect_bounds!(self, area, x, y, line_break); - self[(x, y)] = Cell::default(); - self[(x, y)] - .set_fg(fg_color) - .set_bg(bg_color) - .set_attrs(attrs) - .set_empty(true); - } - _ => {} - } - x += 1; - } - (x, y) + #[inline(always)] + pub fn generation(&self) -> ScreenGeneration { + self.area.generation() } - pub fn copy_area_with_break(&mut self, grid_src: &Self, dest: Area, src: Area) -> Pos { - if !is_valid_area!(dest) || !is_valid_area!(src) { - log::debug!( - "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", - src, - dest - ); - return upper_left!(dest); - } - - if grid_src.is_empty() || self.is_empty() { - return upper_left!(dest); - } - - let mut ret = bottom_right!(dest); - let mut src_x = get_x(upper_left!(src)); - let mut src_y = get_y(upper_left!(src)); - - 'y_: for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { - 'x_: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) { - if grid_src[(src_x, src_y)].ch() == '\n' { - src_y += 1; - src_x = 0; - if src_y >= get_y(bottom_right!(src)) { - ret.1 = y; - break 'y_; - } - continue 'y_; - } - - self[(x, y)] = grid_src[(src_x, src_y)]; - src_x += 1; - if src_x >= get_x(bottom_right!(src)) { - src_y += 1; - src_x = 0; - if src_y >= get_y(bottom_right!(src)) { - //clear_area(self, ((get_x(upper_left!(dest)), y), bottom_right!(dest))); - ret.1 = y; - break 'y_; - } - break 'x_; - } + /// Completely clear an `Area` with an empty char and the terminal's default + /// colors. + pub fn clear_area(&mut self, area: Area, attributes: ThemeAttribute) { + for row in self.bounds_iter(area) { + for c in row { + self[c] = Cell::default(); + self[c] + .set_fg(attributes.fg) + .set_bg(attributes.bg) + .set_attrs(attributes.attrs); + } + } + } + + /// Change foreground and background colors in an `Area` + pub fn change_colors(&mut self, area: Area, fg_color: Color, bg_color: Color) { + if cfg!(feature = "debug-tracing") { + let bounds = self.size(); + let upper_left = area.upper_left(); + let bottom_right = area.bottom_right(); + let (x, y) = upper_left; + if y > (get_y(bottom_right)) + || x > get_x(bottom_right) + || y >= get_y(bounds) + || x >= get_x(bounds) + { + log::debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); + return; + } + } + for row in self.bounds_iter(area) { + for c in row { + self[c].set_fg(fg_color).set_bg(bg_color); + } + } + } + + /// Change [`ThemeAttribute`] in an `Area` + pub fn change_theme(&mut self, area: Area, theme: ThemeAttribute) { + if cfg!(feature = "debug-tracing") { + let bounds = self.size(); + let upper_left = area.upper_left(); + let bottom_right = area.bottom_right(); + let (x, y) = upper_left; + if y > (get_y(bottom_right)) + || x > get_x(bottom_right) + || y >= get_y(bounds) + || x >= get_x(bounds) + { + log::debug!("BUG: Invalid area in change_theme:\n area: {:?}", area); + return; + } + } + for row in self.bounds_iter(area) { + for c in row { + self[c] + .set_fg(theme.fg) + .set_bg(theme.bg) + .set_attrs(theme.attrs); } } - ret } /// Copy a source `Area` to a destination. pub fn copy_area(&mut self, grid_src: &Self, dest: Area, src: Area) -> Pos { - if !is_valid_area!(dest) || !is_valid_area!(src) { + debug_assert_eq!(self.generation(), dest.generation()); + debug_assert_eq!(grid_src.generation(), src.generation()); + if self.generation() != dest.generation() || grid_src.generation() != src.generation() { log::debug!( "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest ); - return upper_left!(dest); + return dest.upper_left(); } if grid_src.is_empty() || self.is_empty() { - return upper_left!(dest); + return dest.upper_left(); } - let mut ret = bottom_right!(dest); - let mut src_x = get_x(upper_left!(src)); - let mut src_y = get_y(upper_left!(src)); + let mut ret = dest.bottom_right(); + let mut src_x = get_x(src.upper_left()); + let mut src_y = get_y(src.upper_left()); let (cols, rows) = grid_src.size(); if src_x >= cols || src_y >= rows { log::debug!("BUG: src area outside of grid_src in copy_area",); - return upper_left!(dest); + return dest.upper_left(); } let tag_associations = grid_src.tag_associations(); @@ -645,8 +576,8 @@ impl CellBuffer { .unwrap_or_else(|i| i); let mut stack: std::collections::BTreeSet<&FormatTag> = std::collections::BTreeSet::default(); - for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { - 'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) { + for y in get_y(dest.upper_left())..=get_y(dest.bottom_right()) { + 'for_x: for x in get_x(dest.upper_left())..=get_x(dest.bottom_right()) { let idx = grid_src.pos_to_index(src_x, src_y).unwrap(); while tag_offset < tag_associations.len() && tag_associations[tag_offset].0 <= idx { if tag_associations[tag_offset].2 { @@ -669,17 +600,15 @@ impl CellBuffer { self[(x, y)].set_keep_attrs(true); } } - if src_x >= get_x(bottom_right!(src)) { + if src_x >= get_x(src.bottom_right()) { break 'for_x; } src_x += 1; } - src_x = get_x(upper_left!(src)); + src_x = get_x(src.upper_left()); src_y += 1; - if src_y > get_y(bottom_right!(src)) { - for row in - self.bounds_iter(((get_x(upper_left!(dest)), y + 1), bottom_right!(dest))) - { + if src_y > get_y(src.bottom_right()) { + for row in self.bounds_iter(dest.skip_rows(y + 1 - get_y(dest.upper_left()))) { for c in row { self[c].set_ch(' '); } @@ -691,78 +620,135 @@ impl CellBuffer { ret } - /// Change foreground and background colors in an `Area` - pub fn change_colors(&mut self, area: Area, fg_color: Color, bg_color: Color) { - if cfg!(feature = "debug-tracing") { - let bounds = self.size(); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let (x, y) = upper_left; - if y > (get_y(bottom_right)) - || x > get_x(bottom_right) - || y >= get_y(bounds) - || x >= get_x(bounds) - { - log::debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); - return; - } - if !is_valid_area!(area) { - log::debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); - return; - } - } - for row in self.bounds_iter(area) { - for c in row { - self[c].set_fg(fg_color).set_bg(bg_color); - } - } - } - - /// Change [`ThemeAttribute`] in an `Area` - pub fn change_theme(&mut self, area: Area, theme: ThemeAttribute) { - if cfg!(feature = "debug-tracing") { - let bounds = self.size(); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let (x, y) = upper_left; - if y > (get_y(bottom_right)) - || x > get_x(bottom_right) - || y >= get_y(bounds) - || x >= get_x(bounds) - { - log::debug!("BUG: Invalid area in change_theme:\n area: {:?}", area); - return; - } - if !is_valid_area!(area) { - log::debug!("BUG: Invalid area in change_theme:\n area: {:?}", area); - return; - } - } - for row in self.bounds_iter(area) { - for c in row { - self[c] - .set_fg(theme.fg) - .set_bg(theme.bg) - .set_attrs(theme.attrs); - } - } - } - - /// Completely clear an `Area` with an empty char and the terminal's default + /// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed /// colors. - pub fn clear_area(&mut self, area: Area, attributes: ThemeAttribute) { - if !is_valid_area!(area) { - return; + pub fn write_string( + &mut self, + s: &str, + fg_color: Color, + bg_color: Color, + attrs: Attr, + area: Area, + // The left-most x coordinate. + line_break: Option, + ) -> Pos { + debug_assert_eq!(area.generation(), self.generation()); + if area.generation() != self.generation() { + // [ref:TODO] log error + return (0, 0); } - for row in self.bounds_iter(area) { - for c in row { - self[c] = Cell::default(); - self[c] - .set_fg(attributes.fg) - .set_bg(attributes.bg) - .set_attrs(attributes.attrs); + let mut bounds = self.size(); + let upper_left = area.upper_left(); + let bottom_right = area.bottom_right(); + let (mut x, mut y) = upper_left; + if y == get_y(bounds) || x == get_x(bounds) { + if self.growable { + if !self.resize( + std::cmp::max(self.cols, x + 2), + std::cmp::max(self.rows, y + 2), + None, + ) { + return (x - upper_left.0, y - upper_left.1); + } + bounds = self.size(); + } else { + return (x - upper_left.0, y - upper_left.1); } } + + if y > (get_y(bottom_right)) + || x > get_x(bottom_right) + || y > get_y(bounds) + || x > get_x(bounds) + { + if self.growable { + if !self.resize( + std::cmp::max(self.cols, x + 2), + std::cmp::max(self.rows, y + 2), + None, + ) { + return (x - upper_left.0, y - upper_left.1); + } + } else { + log::debug!(" Invalid area with string {} and area {:?}", s, area); + return (x - upper_left.0, y - upper_left.1); + } + } + for c in s.chars() { + if c == '\r' { + continue; + } + if c == '\n' { + y += 1; + if let Some(_x) = line_break { + x = _x + get_x(upper_left); + continue; + } else { + break; + } + } + if y > get_y(bottom_right) + || x > get_x(bottom_right) + || y > get_y(bounds) + || x > get_x(bounds) + { + if let Some(_x) = line_break { + if !(y > get_y(bottom_right) || y > get_y(bounds)) { + x = _x + get_x(upper_left); + y += 1; + continue; + } + } + break; + } + if c == '\t' { + self[(x, y)].set_ch(' '); + x += 1; + if let Some(c) = self.get_mut(x, y) { + c.set_ch(' '); + } else if let Some(_x) = line_break { + if !(y > get_y(bottom_right) || y > get_y(bounds)) { + x = _x + get_x(upper_left); + y += 1; + continue; + } else { + break; + } + } + } else { + self[(x, y)].set_ch(c); + } + self[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); + + match wcwidth(u32::from(c)) { + Some(0) | None => { + /* Skip drawing zero width characters */ + self[(x, y)].empty = true; + } + Some(2) => { + /* Grapheme takes more than one column, so the next cell will be + * drawn over. Set it as empty to skip drawing it. */ + x += 1; + self[(x, y)] = Cell::default(); + self[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs) + .set_empty(true); + } + _ => {} + } + x += 1; + } + (x - upper_left.0, y - upper_left.1) + } + + #[inline] + pub const fn area(&self) -> Area { + self.area } } @@ -796,14 +782,6 @@ impl IndexMut for CellBuffer { } } -impl Default for CellBuffer { - /// Constructs a new `CellBuffer` with a size of `(0, 0)`, using the default - /// `Cell` as a blank. - fn default() -> Self { - Self::new(0, 0, Cell::default()) - } -} - impl std::fmt::Display for CellBuffer { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { '_y: for y in 0..self.rows { @@ -851,7 +829,7 @@ impl Cell { /// assert_eq!(cell.bg(), Color::Green); /// assert_eq!(cell.attrs(), Attr::DEFAULT); /// ``` - pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Self { + pub const fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Self { Self { ch, fg, @@ -864,6 +842,10 @@ impl Cell { } } + pub const fn new_default() -> Self { + Self::new(' ', Color::Default, Color::Default, Attr::DEFAULT) + } + /// Creates a new `Cell` with the given `char` and default style. /// /// # Examples @@ -1279,127 +1261,137 @@ impl Attr { } } -/// Use `RowIterator` to iterate the cells of a row without the need to do any +/// Use [`RowIterator`] to iterate the cells of a row without the need to do any /// bounds checking; the iterator will simply return `None` when it reaches the -/// end of the row. `RowIterator` can be created via the `CellBuffer::row_iter` -/// method and can be returned by `BoundsIterator` which iterates each row. -/// ```no_run -/// # let mut grid = meli::CellBuffer::new(1, 1, meli::Cell::default()); -/// # let x = 0; -/// for c in grid.row_iter(x..(x + 11), 0) { -/// grid[c].set_ch('w'); +/// end of the row. [`RowIterator`] can be created via the +/// [`CellBuffer::row_iter`] method and can be returned by [`BoundsIterator`] +/// which iterates each row. +/// +/// ```rust,no_run +/// # use meli::terminal::{Screen, Virtual, Area}; +/// # let mut screen = Screen::::new(); +/// # assert!(screen.resize(120, 20)); +/// # let area = screen.area(); +/// for c in screen.grid().row_iter(area, 0..area.width(), 2) { +/// screen.grid_mut()[c].set_ch('g'); /// } /// ``` #[derive(Debug)] pub struct RowIterator { row: usize, - _width: usize, col: std::ops::Range, + area: Area, } -/// `BoundsIterator` iterates each row returning a `RowIterator`. -/// ```no_run -/// # let mut grid = meli::CellBuffer::new(1, 1, meli::Cell::default()); -/// # let area = ((0, 0), (1, 1)); -/// /* Visit each `Cell` in `area`. */ -/// for row in grid.bounds_iter(area) { +/// [`BoundsIterator`] iterates each row returning a [`RowIterator`]. +/// +/// ```rust,no_run +/// # use meli::terminal::{Screen, Virtual, Area}; +/// # let mut screen = Screen::::new(); +/// # assert!(screen.resize(120, 20)); +/// # let area = screen.area(); +/// for row in screen.grid().bounds_iter(area) { /// for c in row { -/// grid[c].set_ch('w'); +/// screen.grid_mut()[c].set_ch('g'); /// } /// } /// ``` -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct BoundsIterator { rows: std::ops::Range, - pub width: usize, - pub height: usize, cols: (usize, usize), + width: usize, + height: usize, + area: Area, +} + +impl std::fmt::Debug for BoundsIterator { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct(stringify!(BoundsIterator)) + .field("rows", &self.rows) + .field("cols", &self.cols) + .field("width", &self.width) + .field("height", &self.height) + .field("is_empty", &self.is_empty()) + .field("bounds_area", &self.area) + .finish() + } } impl BoundsIterator { - const EMPTY: Self = BoundsIterator { - rows: 0..0, - width: 0, - height: 0, - cols: (0, 0), - }; - + #[inline] pub fn area(&self) -> Area { - ( - (self.cols.0, self.rows.start), - ( - std::cmp::max(self.cols.0, self.cols.1.saturating_sub(1)), - std::cmp::max(self.rows.start, self.rows.end.saturating_sub(1)), - ), - ) + self.area } + #[inline] + pub fn width(&self) -> usize { + self.width + } + + #[inline] + pub fn height(&self) -> usize { + self.height + } + + #[inline] pub fn is_empty(&self) -> bool { - self.width == 0 || self.height == 0 || self.rows.len() == 0 + self.area.is_empty() || self.width == 0 || self.height == 0 || self.rows.len() == 0 } - pub fn add_x(&mut self, x: usize) -> Self { + pub fn add_x(&mut self, x: usize) { if x == 0 { - return Self::EMPTY; + return; } - let ret = Self { - rows: self.rows.clone(), - width: self.width.saturating_sub(x), - height: self.height, - cols: self.cols, - }; - if self.cols.0 + x < self.cols.1 && self.width > x { - self.cols.0 += x; - self.width -= x; - return ret; - } - *self = Self::EMPTY; - ret + self.width = self.width.saturating_sub(x); + self.cols.0 += x; + self.cols.0 = self.cols.0.min(self.cols.1); + self.area = self.area.skip_cols(x); } } impl Iterator for BoundsIterator { type Item = RowIterator; fn next(&mut self) -> Option { - if let Some(next_row) = self.rows.next() { - Some(RowIterator { - row: next_row, - _width: self.width, - col: self.cols.0..self.cols.1, - }) - } else { - None - } + let row = self.rows.next()?; + let area = self.area.nth_row(0); + self.area = self.area.skip_rows(1); + self.height = self.area.height(); + Some(RowIterator { + row, + col: self.cols.0..self.cols.1, + area, + }) } } impl Iterator for RowIterator { type Item = (usize, usize); fn next(&mut self) -> Option { - if let Some(next_col) = self.col.next() { - Some((next_col, self.row)) - } else { - None - } + let x = self.col.next()?; + self.area = self.area.skip_cols(1); + Some((x, self.row)) } } impl RowIterator { - pub fn forward_col(mut self, new_val: usize) -> Self { - if self.col.start > new_val { - self - } else if self.col.end <= new_val { - self.col.start = self.col.end; - self - } else { - self.col.start = new_val; - self - } + #[inline] + pub const fn area(&self) -> Area { + self.area } - pub fn area(&self) -> Area { - ((self.col.start, self.row), (self.col.end, self.row)) + #[inline] + pub const fn row_index(&self) -> usize { + self.row + } + + pub const fn empty(generation: ScreenGeneration) -> Self { + Self { + row: 0, + col: 0..0, + area: Area::new_empty(generation), + } } } @@ -1689,11 +1681,9 @@ pub mod boundaries { /// Puts boundaries in `area`. /// Returns the inner area of the created box. pub fn create_box(grid: &mut CellBuffer, area: Area) -> Area { - if !is_valid_area!(area) { - return ((0, 0), (0, 0)); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); + debug_assert_eq!(grid.generation(), area.generation()); + let upper_left = area.upper_left(); + let bottom_right = area.bottom_right(); if !grid.ascii_drawing { for x in get_x(upper_left)..get_x(bottom_right) { @@ -1719,28 +1709,7 @@ pub mod boundaries { set_and_join_box(grid, bottom_right, BoxBoundary::Vertical); } - ( - ( - std::cmp::min( - get_x(upper_left) + 2, - std::cmp::min(get_x(upper_left) + 1, get_x(bottom_right)), - ), - std::cmp::min( - get_y(upper_left) + 2, - std::cmp::min(get_y(upper_left) + 1, get_y(bottom_right)), - ), - ), - ( - std::cmp::max( - get_x(bottom_right).saturating_sub(2), - std::cmp::max(get_x(bottom_right).saturating_sub(1), get_x(upper_left)), - ), - std::cmp::max( - get_y(bottom_right).saturating_sub(2), - std::cmp::max(get_y(bottom_right).saturating_sub(1), get_y(upper_left)), - ), - ), - ) + area.skip(1, 1).skip_rows_from_end(1).skip_cols_from_end(1) } } @@ -1833,34 +1802,96 @@ pub enum WidgetWidth { #[cfg(test)] mod tests { - use melib::text_processing::{Reflow, TextProcessing, _ALICE_CHAPTER_1}; + use crate::terminal::{Screen, Virtual}; - use super::*; + //use melib::text_processing::{Reflow, TextProcessing, _ALICE_CHAPTER_1}; #[test] fn test_cellbuffer_search() { - let lines: Vec = _ALICE_CHAPTER_1.split_lines_reflow(Reflow::All, Some(78)); - let mut buf = CellBuffer::new( - lines.iter().map(String::len).max().unwrap(), - lines.len(), - Cell::with_char(' '), - ); - let width = buf.size().0; - for (i, l) in lines.iter().enumerate() { - buf.write_string( - l, - Color::Default, - Color::Default, - Attr::DEFAULT, - ((0, i), (width.saturating_sub(1), i)), - None, - ); - } - for ind in buf.kmp_search("Alice") { - for c in &buf.cellvec()[ind..std::cmp::min(buf.cellvec().len(), ind + 25)] { - print!("{}", c.ch()); + //let lines: Vec = + // _ALICE_CHAPTER_1.split_lines_reflow(Reflow::All, Some(78)); + // let mut buf = CellBuffer::new( + // lines.iter().map(String::len).max().unwrap(), + // lines.len(), + // Cell::with_char(' '), + //); + //let width = buf.size().0; + //for (i, l) in lines.iter().enumerate() { + // buf.write_string( + // l, + // Color::Default, + // Color::Default, + // Attr::DEFAULT, + // ((0, i), (width.saturating_sub(1), i)), + // None, + // ); + //} + //for ind in buf.kmp_search("Alice") { + // for c in &buf.cellvec()[ind..std::cmp::min(buf.cellvec().len(), + // ind + 25)] { print!("{}", c.ch()); + // } + // println!(); + //} + } + + #[test] + fn test_bounds_iter() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + let mut full_bounds = screen.grid().bounds_iter(area); + assert_eq!(full_bounds.area(), area); + assert_eq!(full_bounds.width(), area.width()); + assert_eq!(full_bounds.height(), area.height()); + assert!(!full_bounds.is_empty()); + + full_bounds.add_x(0); + assert_eq!(full_bounds.area(), area); + + full_bounds.add_x(1); + assert_eq!(full_bounds.area().width(), area.width() - 1); + full_bounds.add_x(area.width()); + assert_eq!(full_bounds.width(), 0); + assert_eq!(full_bounds.area().width(), 0); + + let full_bounds = screen.grid().bounds_iter(area); + let row_iters = full_bounds.into_iter().collect::>(); + assert_eq!(row_iters.len(), area.height()); + + for mid in 0..row_iters.len() { + assert_eq!(mid, row_iters[mid].row_index()); + let (left, right) = row_iters.as_slice().split_at(mid); + let mid = &right[0]; + assert!(area.contains(mid.area())); + for l in left { + assert!(area.contains(l.area())); + assert!(!mid.area().contains(l.area())); } - println!(); + for r in &right[1..] { + assert!(area.contains(r.area())); + assert!(!mid.area().contains(r.area())); + } + } + + let inner_area = area.place_inside((60, 10), true, true); + let bounds = screen.grid().bounds_iter(inner_area); + let row_iters = bounds.into_iter().collect::>(); + assert_eq!(row_iters.len(), inner_area.height()); + + for mut row in row_iters { + let row_index = row.row_index(); + assert_eq!(row.area().width(), 61); + assert_eq!(row.next(), Some((2, row_index))); + assert_eq!(row.area().width(), 60); + assert_eq!( + &row.collect::>(), + &(3..63) + .zip(std::iter::repeat(row_index)) + .collect::>() + ); } } } diff --git a/meli/src/terminal/embed.rs b/meli/src/terminal/embed.rs index 60d28933..593486c5 100644 --- a/meli/src/terminal/embed.rs +++ b/meli/src/terminal/embed.rs @@ -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>, 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_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::::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::::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; } diff --git a/meli/src/terminal/position.rs b/meli/src/terminal/position.rs index 5ad8221f..270c6d92 100644 --- a/meli/src/terminal/position.rs +++ b/meli/src/terminal/position.rs @@ -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 { - 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)) -} diff --git a/meli/src/terminal/screen.rs b/meli/src/terminal/screen.rs index 9edc9fe2..ad4cea2d 100644 --- a/meli/src/terminal/screen.rs +++ b/meli/src/terminal/screen.rs @@ -22,112 +22,245 @@ //! Terminal grid cells, keys, colors, etc. use std::io::{BufWriter, Write}; -use melib::log; +use melib::{log, uuid}; use termion::{clear, cursor, raw::IntoRawMode, screen::AlternateScreen}; use crate::{ - cells::CellBuffer, terminal::{ - BracketModeEnd, BracketModeStart, Color, DisableMouse, DisableSGRMouse, EnableMouse, - EnableSGRMouse, RestoreWindowTitleIconFromStack, SaveWindowTitleIconToStack, + cells::CellBuffer, position::*, BracketModeEnd, BracketModeStart, Cell, Color, + DisableMouse, DisableSGRMouse, EnableMouse, EnableSGRMouse, + RestoreWindowTitleIconFromStack, SaveWindowTitleIconToStack, }, - Attr, + Attr, Context, }; -pub type StateStdout = - termion::screen::AlternateScreen>>; +pub type StateStdout = termion::screen::AlternateScreen< + termion::raw::RawTerminal>>, +>; type DrawHorizontalSegmentFn = fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (); -pub struct Screen { - pub cols: usize, - pub rows: usize, - pub grid: CellBuffer, - pub overlay_grid: CellBuffer, - pub stdout: Option, - pub mouse: bool, - pub draw_horizontal_segment_fn: DrawHorizontalSegmentFn, +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] +#[repr(transparent)] +pub struct ScreenGeneration((u64, u64)); + +impl ScreenGeneration { + pub const NIL: ScreenGeneration = Self((0, 0)); + + #[inline] + pub fn next(self) -> Self { + Self(uuid::Uuid::new_v4().as_u64_pair()) + } } -impl Screen { - /// Switch back to the terminal's main screen (The command line the user - /// sees before opening the application) - pub fn switch_to_main_screen(&mut self) { - let mouse = self.mouse; - write!( - self.stdout.as_mut().unwrap(), - "{}{}{}{}{disable_sgr_mouse}{disable_mouse}", - termion::screen::ToMainScreen, - cursor::Show, - RestoreWindowTitleIconFromStack, - BracketModeEnd, - disable_sgr_mouse = if mouse { DisableSGRMouse.as_ref() } else { "" }, - disable_mouse = if mouse { DisableMouse.as_ref() } else { "" }, - ) - .unwrap(); - self.flush(); - self.stdout = None; +impl Default for ScreenGeneration { + fn default() -> Self { + Self::NIL + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Virtual; + +pub struct Tty { + stdout: Option, + mouse: bool, + draw_horizontal_segment_fn: DrawHorizontalSegmentFn, +} + +impl Tty { + #[inline] + pub fn stdout_mut(&mut self) -> Option<&mut StateStdout> { + self.stdout.as_mut() } - pub fn switch_to_alternate_screen(&mut self, context: &crate::Context) { - let s = std::io::stdout(); - let s = BufWriter::with_capacity(240 * 80, s); - - let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap()); - - write!( - &mut stdout, - "{save_title_to_stack}{}{}{}{window_title}{}{}{enable_mouse}{enable_sgr_mouse}", - termion::screen::ToAlternateScreen, - cursor::Hide, - clear::All, - cursor::Goto(1, 1), - BracketModeStart, - save_title_to_stack = SaveWindowTitleIconToStack, - window_title = if let Some(ref title) = context.settings.terminal.window_title { - format!("\x1b]2;{}\x07", title) - } else { - String::new() - }, - enable_mouse = if self.mouse { EnableMouse.as_ref() } else { "" }, - enable_sgr_mouse = if self.mouse { - EnableSGRMouse.as_ref() - } else { - "" - }, - ) - .unwrap(); - - self.stdout = Some(stdout); - self.flush(); + #[inline] + pub fn draw_fn(&self) -> DrawHorizontalSegmentFn { + self.draw_horizontal_segment_fn } - pub fn flush(&mut self) { - if let Some(s) = self.stdout.as_mut() { - s.flush().unwrap(); + #[inline] + pub fn mouse(&self) -> bool { + self.mouse + } + + #[inline] + pub fn set_mouse(&mut self, mouse: bool) -> &mut Self { + self.mouse = mouse; + self + } + + #[inline] + pub fn set_draw_fn( + &mut self, + draw_horizontal_segment_fn: DrawHorizontalSegmentFn, + ) -> &mut Self { + self.draw_horizontal_segment_fn = draw_horizontal_segment_fn; + self + } +} + +mod private { + pub trait Sealed {} +} +impl private::Sealed for Virtual {} +impl private::Sealed for Tty {} + +pub struct Screen { + cols: usize, + rows: usize, + grid: CellBuffer, + overlay_grid: CellBuffer, + display: Display, + generation: ScreenGeneration, +} + +impl std::fmt::Debug for Screen { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct(stringify!(Screen)) + .field("cols", &self.cols) + .field("rows", &self.rows) + .field("grid", &self.grid) + .field("overlay_grid", &self.overlay_grid) + .field("generation", &self.generation) + .finish() + } +} + +impl Screen { + #[inline] + pub fn init(display: D) -> Self { + let area = Area { + offset: (0, 0), + upper_left: (0, 0), + bottom_right: (0, 0), + empty: true, + canvas_cols: 0, + canvas_rows: 0, + generation: ScreenGeneration::NIL, + }; + Self { + cols: 0, + rows: 0, + grid: CellBuffer::nil(area), + overlay_grid: CellBuffer::nil(area), + display, + generation: ScreenGeneration::NIL, } } - pub fn set_mouse(&mut self, value: bool) { - if let Some(stdout) = self.stdout.as_mut() { - write!( - stdout, - "{mouse}{sgr_mouse}", - mouse = if value { - AsRef::::as_ref(&EnableMouse) - } else { - AsRef::::as_ref(&DisableMouse) - }, - sgr_mouse = if value { - AsRef::::as_ref(&EnableSGRMouse) - } else { - AsRef::::as_ref(&DisableSGRMouse) - }, - ) - .unwrap(); + #[inline] + pub fn with_cols_and_rows(mut self, cols: usize, rows: usize) -> Self { + self.generation = self.generation.next(); + self.cols = cols; + self.rows = rows; + Self { + cols, + rows, + grid: CellBuffer::new(Cell::with_char(' '), self.area()), + overlay_grid: CellBuffer::new(Cell::with_char(' '), self.area()), + ..self } - self.flush(); } + + pub const fn area(&self) -> Area { + let upper_left = (0, 0); + let bottom_right = (self.cols.saturating_sub(1), self.rows.saturating_sub(1)); + Area { + offset: upper_left, + upper_left, + bottom_right, + empty: matches!((self.cols, self.rows), (0, 0)), + canvas_cols: self.cols, + canvas_rows: self.rows, + generation: self.generation, + } + } + + #[inline] + pub fn grid(&self) -> &CellBuffer { + &self.grid + } + + #[inline] + pub fn grid_mut(&mut self) -> &mut CellBuffer { + &mut self.grid + } + + #[inline] + pub fn overlay_grid(&self) -> &CellBuffer { + &self.overlay_grid + } + + #[inline] + pub fn overlay_grid_mut(&mut self) -> &mut CellBuffer { + &mut self.overlay_grid + } + + #[inline] + pub fn grid_and_overlay_grid_mut(&mut self) -> (&mut CellBuffer, &mut CellBuffer) { + (&mut self.grid, &mut self.overlay_grid) + } + + #[inline(always)] + pub const fn generation(&self) -> ScreenGeneration { + self.generation + } + + #[inline] + pub const fn cols(&self) -> usize { + self.cols + } + + #[inline] + pub const fn rows(&self) -> usize { + self.rows + } +} + +impl Clone for Screen { + fn clone(&self) -> Self { + Self { + grid: self.grid.clone(), + overlay_grid: self.overlay_grid.clone(), + ..*self + } + } +} + +impl Screen { + #[inline] + pub fn new() -> Self { + Self::init(Tty { + stdout: None, + mouse: false, + draw_horizontal_segment_fn: Screen::draw_horizontal_segment, + }) + } + + #[inline] + pub fn with_tty(self, display: Tty) -> Self { + Self { display, ..self } + } + + #[inline] + pub fn tty(&self) -> &Tty { + &self.display + } + + #[inline] + pub fn tty_mut(&mut self) -> &mut Tty { + &mut self.display + } + + #[inline] + pub fn draw(&mut self, x_start: usize, x_end: usize, y: usize) { + let Some(stdout) = self.display.stdout.as_mut() else { + return; + }; + (self.display.draw_horizontal_segment_fn)(&mut self.grid, stdout, x_start, x_end, y); + } + /// On `SIGWNICH` the `State` redraws itself according to the new /// terminal size. pub fn update_size(&mut self) { @@ -145,15 +278,80 @@ impl Screen { termrows ); } - self.cols = termcols.unwrap_or(72) as usize; - self.rows = termrows.unwrap_or(120) as usize; - if !self.grid.resize(self.cols, self.rows, None) { - panic!( - "Terminal size too big: ({} cols, {} rows)", - self.cols, self.rows - ); + let cols = termcols.unwrap_or(72) as usize; + let rows = termrows.unwrap_or(120) as usize; + if self.grid.resize(cols, rows, None) && self.overlay_grid.resize(cols, rows, None) { + self.generation = self.generation.next(); + self.cols = cols; + self.rows = rows; + self.grid.area = self.area(); + self.overlay_grid.area = self.area(); + } else { + log::warn!("Terminal size too big: ({} cols, {} rows)", cols, rows); + } + } + + /// Switch back to the terminal's main screen (The command line the user + /// sees before opening the application) + pub fn switch_to_main_screen(&mut self) { + let Some(stdout) = self.display.stdout.as_mut() else { + return; + }; + let mouse = self.display.mouse; + write!( + stdout, + "{}{}{}{}{disable_sgr_mouse}{disable_mouse}", + termion::screen::ToMainScreen, + cursor::Show, + RestoreWindowTitleIconFromStack, + BracketModeEnd, + disable_sgr_mouse = if mouse { DisableSGRMouse.as_ref() } else { "" }, + disable_mouse = if mouse { DisableMouse.as_ref() } else { "" }, + ) + .unwrap(); + self.flush(); + self.display.stdout = None; + } + + pub fn switch_to_alternate_screen(&mut self, context: &crate::Context) { + let mut stdout = BufWriter::with_capacity(240 * 80, Box::new(std::io::stdout()) as _); + + write!( + &mut stdout, + "{save_title_to_stack}{}{}{}{window_title}{}{}{enable_mouse}{enable_sgr_mouse}", + termion::screen::ToAlternateScreen, + cursor::Hide, + clear::All, + cursor::Goto(1, 1), + BracketModeStart, + save_title_to_stack = SaveWindowTitleIconToStack, + window_title = if let Some(ref title) = context.settings.terminal.window_title { + format!("\x1b]2;{}\x07", title) + } else { + String::new() + }, + enable_mouse = if self.display.mouse { + EnableMouse.as_ref() + } else { + "" + }, + enable_sgr_mouse = if self.display.mouse { + EnableSGRMouse.as_ref() + } else { + "" + }, + ) + .unwrap(); + + self.display.stdout = Some(AlternateScreen::from(stdout.into_raw_mode().unwrap())); + self.flush(); + } + + #[inline] + pub fn flush(&mut self) { + if let Some(stdout) = self.display.stdout.as_mut() { + stdout.flush().unwrap(); } - let _ = self.overlay_grid.resize(self.cols, self.rows, None); } /// Draw only a specific `area` on the screen. @@ -221,3 +419,751 @@ impl Screen { } } } + +impl Default for Screen { + fn default() -> Self { + Self::new() + } +} + +impl Screen { + #[inline] + pub fn new() -> Self { + Self::init(Virtual) + } + + #[must_use] + pub fn resize(&mut self, cols: usize, rows: usize) -> bool { + if self.grid.resize(cols, rows, None) && self.overlay_grid.resize(cols, rows, None) { + self.generation = self.generation.next(); + self.cols = cols; + self.rows = rows; + self.grid.area = self.area(); + self.overlay_grid.area = self.area(); + return true; + } + + false + } + + #[must_use] + pub fn resize_with_context(&mut self, cols: usize, rows: usize, context: &Context) -> bool { + if self.grid.resize_with_context(cols, rows, context) + && self.overlay_grid.resize_with_context(cols, rows, context) + { + self.generation = self.generation.next(); + self.cols = cols; + self.rows = rows; + self.grid.area = self.area(); + self.overlay_grid.area = self.area(); + return true; + } + + false + } +} + +/// An `Area` consists of two points: the upper left and bottom right corners. +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +pub struct Area { + offset: Pos, + upper_left: Pos, + bottom_right: Pos, + empty: bool, + canvas_cols: usize, + canvas_rows: usize, + generation: ScreenGeneration, +} + +impl std::fmt::Debug for Area { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct(stringify!(Area)) + .field("width", &self.width()) + .field("height", &self.height()) + .field("offset", &self.offset) + .field("upper_left", &self.upper_left) + .field("bottom_right", &self.bottom_right) + .field("empty flag", &self.empty) + .field("is_empty", &self.is_empty()) + .field("canvas_cols", &self.canvas_cols) + .field("canvas_rows", &self.canvas_rows) + .field("generation", &self.generation) + .finish() + } +} + +impl From<&Screen> for Area { + fn from(sc: &Screen) -> Self { + sc.area() + } +} + +impl Area { + #[inline] + pub fn height(&self) -> usize { + if self.is_empty() { + return 0; + } + get_y(self.bottom_right).saturating_sub(get_y(self.upper_left)) + 1 + } + + #[inline] + pub fn width(&self) -> usize { + if self.is_empty() { + return 0; + } + get_x(self.bottom_right).saturating_sub(get_x(self.upper_left)) + 1 + } + + #[inline] + pub fn size(&self) -> (usize, usize) { + (self.width(), self.height()) + } + + /// Get `n`th row of `area` or its last one. + #[inline] + pub fn nth_row(&self, n: usize) -> Self { + let Self { + offset, + upper_left, + bottom_right, + empty, + canvas_cols, + canvas_rows, + generation, + } = *self; + let (_, max_y) = bottom_right; + let n = std::cmp::min(n, self.height()); + if self.is_empty() || max_y < (get_y(upper_left) + n) { + return self.into_empty(); + } + let y = std::cmp::min(max_y, get_y(upper_left) + n); + Self { + offset: pos_inc(offset, (0, n)), + upper_left: set_y(upper_left, y), + bottom_right: set_y(bottom_right, y), + empty, + canvas_cols, + canvas_rows, + generation, + } + } + + /// Get `n`th col of `area` or its last one. + #[inline] + pub fn nth_col(&self, n: usize) -> Self { + let Self { + offset, + upper_left, + bottom_right, + empty, + canvas_cols, + canvas_rows, + generation, + } = *self; + let (max_x, _) = bottom_right; + let n = std::cmp::min(n, self.width()); + if self.is_empty() || max_x < (get_x(upper_left) + n) { + return self.into_empty(); + } + let x = std::cmp::min(max_x, get_x(upper_left) + n); + Self { + offset: pos_inc(offset, (x, 0)), + upper_left: set_x(upper_left, x), + bottom_right: set_x(bottom_right, x), + empty, + canvas_cols, + canvas_rows, + generation, + } + } + + /// Place box given by `(width, height)` in corner of `area` + pub fn place_inside(&self, (width, height): (usize, usize), upper: bool, left: bool) -> Self { + if self.is_empty() || width < 3 || height < 3 { + return *self; + } + let (upper_x, upper_y) = self.upper_left; + let (max_x, max_y) = self.bottom_right; + 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) + }; + let upper_left = (std::cmp::min(x, max_x), std::cmp::min(y, max_y)); + let bottom_right = ( + std::cmp::min(x + width, max_x), + std::cmp::min(y + height, max_y), + ); + + Self { + offset: pos_inc( + self.offset, + ( + (get_x(upper_left) - get_x(self.upper_left)), + (get_y(upper_left) - get_y(self.upper_left)), + ), + ), + upper_left, + bottom_right, + empty: self.empty, + canvas_cols: self.canvas_cols, + canvas_rows: self.canvas_rows, + generation: self.generation, + } + } + + /// Place given area of dimensions `(width, height)` inside `area` according + /// to given alignment + pub fn align_inside( + &self, + (width, height): (usize, usize), + horizontal_alignment: Alignment, + vertical_alignment: Alignment, + ) -> Self { + if self.is_empty() || width == 0 || height == 0 { + return *self; + } + let (top_x, width) = match horizontal_alignment { + Alignment::Center => ( + { std::cmp::max(self.width() / 2, width / 2) - width / 2 }, + width, + ), + Alignment::Start => (0, self.width().min(width)), + Alignment::End => (self.width().saturating_sub(width), self.width().min(width)), + Alignment::Fill => (0, self.width()), + }; + let (top_y, height) = match vertical_alignment { + Alignment::Center => ( + { std::cmp::max(self.height() / 2, height / 2) - height / 2 }, + self.height().min(height), + ), + Alignment::Start => (0, self.height().min(height)), + Alignment::End => (self.height().saturating_sub(height), self.height()), + Alignment::Fill => (0, self.height()), + }; + + self.skip(top_x, top_y).take(width, height) + } + + /// Place box given by `dimensions` in center of `area` + #[inline] + pub fn center_inside(&self, dimensions: (usize, usize)) -> Self { + self.align_inside(dimensions, Alignment::Center, Alignment::Center) + } + + #[inline] + pub fn contains(&self, other: Self) -> bool { + debug_assert_eq!(self.generation, other.generation); + if self.is_empty() { + return false; + } else if other.is_empty() { + return true; + } + get_y(other.bottom_right) <= get_y(self.bottom_right) + && get_x(other.upper_left) >= get_x(self.upper_left) + && get_y(other.upper_left) >= get_y(self.upper_left) + && get_x(other.bottom_right) <= get_x(self.bottom_right) + } + + /// Skip `n` rows and return the remaining area. + /// Return value will be an empty area if `n` is more than the height. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Skip first two rows: + /// let body = area.skip_rows(2); + /// assert_eq!(body.height(), 18); + /// ``` + #[inline] + pub fn skip_rows(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.height()); + if self.is_empty() || self.upper_left.1 + n > self.bottom_right.1 { + return self.into_empty(); + } + + Self { + offset: pos_inc(self.offset, (0, n)), + upper_left: pos_inc(self.upper_left, (0, n)), + ..*self + } + } + + /// Skip the last `n` rows and return the remaining area. + /// Return value will be an empty area if `n` is more than the height. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Take only first two rows (equivalent to area.take_rows(2)) + /// let header = area.skip_rows_from_end(18); + /// assert_eq!(header.height(), 2); + /// assert_eq!(header, area.take_rows(2)); + /// ``` + #[inline] + pub fn skip_rows_from_end(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.height()); + if self.is_empty() || self.bottom_right.1 < n { + return self.into_empty(); + } + + Self { + bottom_right: (self.bottom_right.0, self.bottom_right.1 - n), + ..*self + } + } + + /// Skip the first `n` rows and return the remaining area. + /// Return value will be an empty area if `n` is more than the width. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Skip first two columns + /// let indent = area.skip_cols(2); + /// assert_eq!(indent.width(), 118); + /// ``` + #[inline] + pub fn skip_cols(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.width()); + if self.is_empty() || self.bottom_right.0 < self.upper_left.0 + n { + return self.into_empty(); + } + + Self { + offset: pos_inc(self.offset, (n, 0)), + upper_left: pos_inc(self.upper_left, (n, 0)), + ..*self + } + } + + /// Skip the last `n` rows and return the remaining area. + /// Return value will be an empty area if `n` is more than the width. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Skip last two columns + /// let indent = area.skip_cols_from_end(2); + /// assert_eq!(indent.width(), 118); + /// assert_eq!(indent, area.take_cols(118)); + /// ``` + #[inline] + pub fn skip_cols_from_end(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.width()); + if self.is_empty() || self.bottom_right.0 < n { + return self.into_empty(); + } + Self { + bottom_right: (self.bottom_right.0 - n, self.bottom_right.1), + ..*self + } + } + + /// Shortcut for using `Area::skip_cols` and `Area::skip_rows` together. + #[inline] + pub fn skip(&self, n_cols: usize, n_rows: usize) -> Self { + self.skip_cols(n_cols).skip_rows(n_rows) + } + + /// Take the first `n` rows and return the remaining area. + /// Return value will be an empty area if `n` is more than the height. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Take only first two rows + /// let header = area.take_rows(2); + /// assert_eq!(header.height(), 2); + /// ``` + #[inline] + pub fn take_rows(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.height()); + if self.is_empty() || self.bottom_right.1 < (self.height() - n) { + return self.into_empty(); + } + + Self { + bottom_right: ( + self.bottom_right.0, + self.bottom_right.1 - (self.height() - n), + ), + ..*self + } + } + + /// Take the first `n` columns and return the remaining area. + /// Return value will be an empty area if `n` is more than the width. + /// + /// # Examples + /// + /// ```rust + /// # use meli::terminal::{Screen, Virtual, Area}; + /// # let mut screen = Screen::::new(); + /// # assert!(screen.resize(120, 20)); + /// # let area = screen.area(); + /// assert_eq!(area.width(), 120); + /// assert_eq!(area.height(), 20); + /// // Take only first two columns + /// let header = area.take_cols(2); + /// assert_eq!(header.width(), 2); + /// ``` + #[inline] + pub fn take_cols(&self, n: usize) -> Self { + let n = std::cmp::min(n, self.width()); + if self.is_empty() || self.bottom_right.0 < (self.width() - n) { + return self.into_empty(); + } + + Self { + bottom_right: ( + self.bottom_right.0 - (self.width() - n), + self.bottom_right.1, + ), + ..*self + } + } + + /// Shortcut for using `Area::take_cols` and `Area::take_rows` together. + #[inline] + pub fn take(&self, n_cols: usize, n_rows: usize) -> Self { + self.take_cols(n_cols).take_rows(n_rows) + } + + #[inline] + pub const fn upper_left(&self) -> Pos { + self.upper_left + } + + #[inline] + pub const fn bottom_right(&self) -> Pos { + self.bottom_right + } + + #[inline] + pub const fn offset(&self) -> Pos { + self.offset + } + + #[inline] + pub const fn generation(&self) -> ScreenGeneration { + self.generation + } + + #[inline] + pub const fn new_empty(generation: ScreenGeneration) -> Self { + Self { + offset: (0, 0), + upper_left: (0, 0), + bottom_right: (0, 0), + canvas_rows: 0, + canvas_cols: 0, + empty: true, + generation, + } + } + + #[inline] + pub const fn into_empty(self) -> Self { + Self { + offset: (0, 0), + upper_left: (0, 0), + bottom_right: (0, 0), + empty: true, + ..self + } + } + + #[inline] + pub const fn is_empty(&self) -> bool { + self.empty + || (self.upper_left.0 > self.bottom_right.0 || self.upper_left.1 > self.bottom_right.1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_skip_rows() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.height() { + assert_eq!(area.skip_rows(i).height(), area.height() - i); + assert!(area.contains(area.skip_rows(i))); + if i < area.height() { + assert!(!area.take_rows(i).contains(area.skip_rows(i))); + } else { + assert!(area.take_rows(i).contains(area.skip_rows(i))); + } + } + + assert!(area.skip_rows(area.height()).is_empty()); + assert_eq!(area.skip_rows(0), area); + } + + #[test] + fn test_skip_rows_from_end() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.height() { + assert_eq!(area.skip_rows_from_end(i).height(), area.height() - i); + assert!(area.contains(area.skip_rows_from_end(i))); + } + + assert!(area.skip_rows_from_end(area.height()).is_empty()); + assert_eq!(area.skip_rows_from_end(0), area); + } + + #[test] + fn test_skip_cols() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.width() { + assert_eq!(area.skip_cols(i).width(), area.width() - i); + assert!(area.contains(area.skip_cols(i))); + if i < area.width() { + assert!(!area.take_cols(i).contains(area.skip_cols(i))); + } else { + assert!(area.take_cols(i).contains(area.skip_cols(i))); + } + } + + assert!(area.skip_cols(area.width()).is_empty()); + assert_eq!(area.skip_cols(0), area); + } + + #[test] + fn test_skip_cols_from_end() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.width() { + assert_eq!(area.skip_cols_from_end(i).width(), area.width() - i); + assert!(area.contains(area.skip_cols_from_end(i))); + } + + assert!(area.skip_cols_from_end(area.width()).is_empty()); + assert_eq!(area.skip_cols_from_end(0), area); + } + + #[test] + fn test_take_rows() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.height() { + assert_eq!(area.take_rows(i).height(), i); + assert!(area.contains(area.take_rows(i))); + } + + assert!(area.take_rows(0).is_empty()); + assert_eq!(area.take_rows(area.height()), area); + } + + #[test] + fn test_take_cols() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 1..=area.width() { + assert_eq!(area.take_cols(i).width(), i); + assert!(area.contains(area.take_cols(i))); + } + + assert!(area.take_cols(0).is_empty()); + assert_eq!(area.take_cols(area.width()), area); + } + + #[test] + fn test_nth_area() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for i in 0..area.width() { + assert_eq!(area.nth_col(i).width(), 1); + assert!(area.contains(area.nth_col(i))); + if i + 1 == area.width() { + assert!(area.nth_col(i).contains(area.nth_col(i + 1))); + } else { + assert!(!area.nth_col(i).contains(area.nth_col(i + 1))); + } + } + + for i in 0..area.height() { + assert_eq!(area.nth_row(i).height(), 1); + assert!(area.contains(area.nth_row(i))); + if i + 1 == area.height() { + assert!(area.nth_row(i).contains(area.nth_row(i + 1))); + } else { + assert!(!area.nth_row(i).contains(area.nth_row(i + 1))); + } + } + } + + #[test] + fn test_place_inside_area() { + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + for width in 0..area.width() { + for height in 0..area.height() { + for upper in [true, false] { + for left in [true, false] { + let inner = area.place_inside((width, height), upper, left); + assert!(area.contains(inner)); + if (3..area.height() - 2).contains(&height) + && (3..area.width() - 2).contains(&width) + { + assert_ne!(area, inner); + } + } + } + } + } + } + + #[test] + fn test_align_inside_area() { + use Alignment::{Center, End, Fill, Start}; + + const ALIGNMENTS: [Alignment; 4] = [Fill, Start, End, Center]; + + let mut screen = Screen::::new(); + assert!(screen.resize(120, 20)); + let area = screen.area(); + assert_eq!(area.width(), 120); + assert_eq!(area.height(), 20); + + // Ask for all subsets that area has: + for width in 1..=area.width() { + for height in 1..=area.height() { + for horz in ALIGNMENTS { + for vert in ALIGNMENTS { + let inner = area.align_inside((width, height), horz, vert); + assert!(area.contains(inner)); + assert!(!inner.is_empty()); + match (horz, vert) { + (Fill, Fill) => { + assert_eq!(area, inner); + } + (Fill, _) => { + assert_eq!(inner.width(), area.width()); + assert_eq!(inner.height(), height); + } + (_, Fill) => { + assert_eq!(inner.height(), area.height()); + assert_eq!(inner.width(), width); + } + _ => { + assert_eq!((width, height), inner.size()); + if (width, height) != area.size() { + assert_ne!(area, inner); + } + } + } + } + } + } + } + + // Ask for more width/height than area has: + for width in 1..=(2 * area.width()) { + for height in 1..=(2 * area.height()) { + for horz in ALIGNMENTS { + for vert in ALIGNMENTS { + let inner = area.align_inside((width, height), horz, vert); + assert!(area.contains(inner)); + assert!(!inner.is_empty()); + match (horz, vert) { + (Fill, Fill) => { + assert_eq!(area, inner); + } + (Fill, _) => { + assert_eq!(inner.width(), area.width()); + assert!( + height >= inner.height() && area.height() >= inner.height() + ); + } + (_, Fill) => { + assert_eq!(inner.height(), area.height()); + assert!(width >= inner.width() && area.width() >= inner.width()); + } + _ => { + assert!( + height >= inner.height() && area.height() >= inner.height() + ); + assert!(width >= inner.width() && area.width() >= inner.width()); + } + } + } + } + } + } + } +} diff --git a/meli/src/utilities.rs b/meli/src/utilities.rs index a54bed6d..dded7ea5 100644 --- a/meli/src/utilities.rs +++ b/meli/src/utilities.rs @@ -36,8 +36,9 @@ pub use self::text::*; mod widgets; pub use self::widgets::*; -mod layouts; -pub use self::layouts::*; +// TODO: remove +//mod layouts; +//pub use self::layouts::*; mod dialogs; pub use self::dialogs::*; @@ -70,7 +71,7 @@ pub struct StatusBar { display_buffer: String, mode: UIMode, mouse: bool, - height: usize, + status_bar_height: usize, dirty: bool, id: ComponentId, progress_spinner: ProgressSpinner, @@ -115,7 +116,7 @@ impl StatusBar { dirty: true, mode: UIMode::Normal, mouse: context.settings.terminal.use_mouse.is_true(), - height: 1, + status_bar_height: 1, id: ComponentId::default(), auto_complete: AutoComplete::new(Vec::new()), progress_spinner, @@ -139,7 +140,7 @@ impl StatusBar { area, None, ); - for c in grid.row_iter(x..(get_x(bottom_right!(area)) + 1), y) { + for c in grid.row_iter(area, x..(get_x(area.bottom_right()) + 1), y) { grid[c] .set_ch(' ') .set_fg(attribute.fg) @@ -147,11 +148,11 @@ impl StatusBar { .set_attrs(attribute.attrs); } let offset = self.status.find('|').unwrap_or(self.status.len()); - if y < get_y(bottom_right!(area)) + 1 { - for x in get_x(upper_left!(area)) + if y < get_y(area.bottom_right()) + 1 { + for x in get_x(area.upper_left()) ..std::cmp::min( - get_x(upper_left!(area)) + offset, - get_x(bottom_right!(area)), + get_x(area.upper_left()) + offset, + get_x(area.bottom_right()), ) { grid[(x, y)].set_attrs(attribute.attrs | Attr::BOLD); @@ -179,22 +180,20 @@ impl StatusBar { attribute.fg, attribute.bg, attribute.attrs, - ((x + 1, y), bottom_right!(area)), + area.skip_cols(x + 1).skip_rows(y), None, ); } - let (mut x, y) = bottom_right!(area); + let (mut x, y) = area.bottom_right(); if self.progress_spinner.is_active() { x = x.saturating_sub(1 + self.progress_spinner.width); } if self.progress_spinner.is_dirty() { self.progress_spinner.draw( grid, - ( - pos_dec(bottom_right!(area), (self.progress_spinner.width, 0)), - bottom_right!(area), - ), + area.skip_rows(area.height().saturating_sub(1)) + .skip_cols(area.width().saturating_sub(self.progress_spinner.width)), context, ); } @@ -246,7 +245,7 @@ impl StatusBar { ); grid.change_theme(area, command_bar); if let Some(ref mut cell) = grid.get_mut( - pos_inc(upper_left!(area), (self.ex_buffer.cursor(), 0)).0, + pos_inc(area.upper_left(), (self.ex_buffer.cursor(), 0)).0, y, ) { cell.set_attrs(command_bar.attrs | Attr::UNDERLINE); @@ -257,31 +256,21 @@ impl StatusBar { impl Component for StatusBar { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !is_valid_area!(area) { + let total_rows = area.height(); + if total_rows <= self.status_bar_height { return; } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - - let total_rows = get_y(bottom_right) - get_y(upper_left); - if total_rows <= self.height { - return; - } - let height = self.height; self.container.draw( grid, - ( - upper_left, - (get_x(bottom_right), get_y(bottom_right) - height), - ), + area.take_rows(total_rows.saturating_sub(self.status_bar_height)), context, ); self.dirty = false; self.draw_status_bar( grid, - (set_y(upper_left, get_y(bottom_right)), bottom_right), + area.skip_rows(total_rows.saturating_sub(self.status_bar_height)), context, ); @@ -291,14 +280,8 @@ impl Component for StatusBar { match self.mode { UIMode::Normal => {} UIMode::Command => { - self.draw_command_bar( - grid, - ( - set_y(upper_left, get_y(bottom_right) - height + 1), - set_y(bottom_right, get_y(bottom_right) - height + 1), - ), - context, - ); + let area = area.skip_rows(total_rows.saturating_sub(self.status_bar_height + 2)); + self.draw_command_bar(grid, area, context); /* don't autocomplete for less than 3 characters */ if self.ex_buffer.as_str().split_graphemes().len() <= 2 { return; @@ -372,40 +355,39 @@ impl Component for StatusBar { self.container.set_dirty(true); } + /* let hist_height = std::cmp::min(15, self.auto_complete.suggestions().len()); - let hist_area = if height < self.auto_complete.suggestions().len() { - let hist_area = ( + let hist_area = if status_bar_height < self.auto_complete.suggestions().len() { + let hist_area = ( + ( + get_x(upper_left), + std::cmp::min( + get_y(bottom_right) - status_bar_height - hist_height + 1, + get_y(pos_dec(bottom_right, (0, status_bar_height))), + ), + ), + pos_dec(bottom_right, (0, status_bar_height)), + ); + ScrollBar::default().set_show_arrows(false).draw( + grid, + hist_area, + context, + self.auto_complete.cursor(), + hist_height, + self.auto_complete.suggestions().len(), + ); + grid.change_theme(hist_area, crate::conf::value(context, "status.history")); + context.dirty_areas.push_back(hist_area); + hist_area + } else { ( get_x(upper_left), std::cmp::min( - get_y(bottom_right) - height - hist_height + 1, - get_y(pos_dec(bottom_right, (0, height))), + get_y(bottom_right) - status_bar_height - hist_height + 1, + get_y(pos_dec(bottom_right, (0, status_bar_height))), ), ), - pos_dec(bottom_right, (0, height)), - ); - ScrollBar::default().set_show_arrows(false).draw( - grid, - hist_area, - context, - self.auto_complete.cursor(), - hist_height, - self.auto_complete.suggestions().len(), - ); - - grid.change_theme(hist_area, crate::conf::value(context, "status.history")); - context.dirty_areas.push_back(hist_area); - hist_area - } else { - ( - ( - get_x(upper_left), - std::cmp::min( - get_y(bottom_right) - height - hist_height + 1, - get_y(pos_dec(bottom_right, (0, height))), - ), - ), - pos_dec(bottom_right, (0, height)), + pos_dec(bottom_right, (0, status_bar_height)), ) }; let offset = if hist_height @@ -454,35 +436,96 @@ impl Component for StatusBar { if y_offset + offset == self.auto_complete.cursor() { grid.change_theme( ( - set_y( - upper_left!(hist_area), - get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1, - ), - set_y( - bottom_right!(hist_area), - get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1, + get_x(upper_left), + std::cmp::min( + get_y(bottom_right) - status_bar_height - hist_height + 1, + get_y(pos_dec(bottom_right, (0, status_bar_height))), ), ), - history_hints, - ); - grid.write_string( - &s.as_str()[self.ex_buffer.as_str().len()..], + pos_dec(bottom_right, (0, status_bar_height)), + ) + }; + let offset = if hist_height + > (self.auto_complete.suggestions().len() - self.auto_complete.cursor()) + { + self.auto_complete.suggestions().len() - hist_height + } else { + self.auto_complete.cursor() + }; + grid.clear_area(hist_area, crate::conf::value(context, "theme_default")); + let history_hints = crate::conf::value(context, "status.history.hints"); + if hist_height > 0 { + grid.change_theme(hist_area, history_hints); + } + for (y_offset, s) in self + .auto_complete + .suggestions() + .iter() + .skip(offset) + .take(hist_height) + .enumerate() + { + let (x, y) = grid.write_string( + s.as_str(), + grid, history_hints.fg, history_hints.bg, history_hints.attrs, ( - ( - get_x(upper_left) - + self.ex_buffer.as_str().split_graphemes().len(), - get_y(bottom_right) - height + 1, + set_y( + hist_area.upper_left(), + get_y(hist_area.bottom_right()) - hist_height + y_offset + 1, ), - set_y(bottom_right, get_y(bottom_right) - height + 1), + hist_area.bottom_right(), ), + Some(get_x(hist_area.upper_left())), + ); + grid.write_string( + &s.description, + grid, + history_hints.fg, + history_hints.bg, + history_hints.attrs, + ((x + 2, y), hist_area.bottom_right()), None, ); + if y_offset + offset == self.auto_complete.cursor() { + grid.change_theme( + ( + set_y( + hist_area.upper_left(), + get_y(hist_area.bottom_right()) - hist_height + + y_offset + + 1, + ), + set_y( + hist_area.bottom_right(), + get_y(hist_area.bottom_right()) - hist_height + + y_offset + + 1, + ), + ), + history_hints, + ); + grid.write_string( + &s.as_str()[self.ex_buffer.as_str().len()..], + history_hints.fg, + history_hints.bg, + history_hints.attrs, + ( + ( + get_x(upper_left) + + self.ex_buffer.as_str().split_graphemes().len(), + get_y(bottom_right) - status_bar_height + 1, + ), + set_y(bottom_right, get_y(bottom_right) - status_bar_height + 1), + ), + None, + ); + } } - } - context.dirty_areas.push_back(hist_area); + context.dirty_areas.push_back(hist_area); + */ } _ => {} } @@ -539,7 +582,7 @@ impl Component for StatusBar { self.mode = *m; match m { UIMode::Normal => { - self.height = 1; + self.status_bar_height = 1; if !self.ex_buffer.is_empty() { context .replies @@ -556,10 +599,10 @@ impl Component for StatusBar { self.ex_buffer_cmd_history_pos.take(); } UIMode::Command => { - self.height = 2; + self.status_bar_height = 2; } _ => { - self.height = 1; + self.status_bar_height = 1; } }; } @@ -810,7 +853,6 @@ pub struct Tabbed { show_shortcuts: bool, help_screen_cursor: (usize, usize), - help_content: CellBuffer, help_curr_views: ShortcutMaps, help_search: Option, theme_default: ThemeAttribute, @@ -827,7 +869,6 @@ impl Tabbed { .get(0) .map(|c| c.shortcuts(context)) .unwrap_or_default(), - help_content: CellBuffer::default(), help_screen_cursor: (0, 0), help_search: None, theme_default: crate::conf::value(context, "theme_default"), @@ -843,8 +884,8 @@ impl Tabbed { } fn draw_tabs(&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 = area.upper_left(); + let bottom_right = area.bottom_right(); let tab_bar_attribute = crate::conf::value(context, "tab.bar"); grid.clear_area(area, tab_bar_attribute); @@ -870,7 +911,7 @@ impl Tabbed { fg, bg, attrs, - (set_x(upper_left, x), bottom_right!(area)), + area.skip_cols(x - get_x(upper_left)), None, ); x = x_ + 1; @@ -906,7 +947,7 @@ impl Tabbed { .set_bg(tab_unfocused_attribute.fg) .set_attrs(tab_unfocused_attribute.attrs); } - for c in grid.row_iter(x..cols, get_y(upper_left)) { + for c in grid.row_iter(area, x..cols, get_y(upper_left)) { grid[c] .set_fg(tab_bar_attribute.fg) .set_bg(tab_bar_attribute.bg) @@ -943,17 +984,9 @@ impl std::fmt::Display for Tabbed { impl Component for Tabbed { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if self.dirty { - grid.clear_area( - ( - upper_left!(area), - set_x(upper_left!(area), get_x(bottom_right!(area))), - ), - crate::conf::value(context, "tab.bar"), - ); - context.dirty_areas.push_back(( - upper_left!(area), - set_x(upper_left!(area), get_x(bottom_right!(area))), - )); + let first_row = area.nth_row(0); + grid.clear_area(first_row, crate::conf::value(context, "tab.bar")); + context.dirty_areas.push_back(first_row); } /* If children are dirty but self isn't and the shortcuts panel is visible, @@ -964,385 +997,369 @@ impl Component for Tabbed { * overwrite the panel on the grid. the drawing order is determined * by the dirty_areas queue which is LIFO */ if self.children.len() > 1 { - self.draw_tabs( - grid, - ( - upper_left!(area), - set_x(upper_left!(area), get_x(bottom_right!(area))), - ), - context, - ); - let y = get_y(upper_left!(area)); - self.children[self.cursor_pos].draw( - grid, - (set_y(upper_left!(area), y + 1), bottom_right!(area)), - context, - ); + self.draw_tabs(grid, area.nth_row(0), context); + self.children[self.cursor_pos].draw(grid, area.skip_rows(1), context); } else { self.children[self.cursor_pos].draw(grid, area, context); } if (self.show_shortcuts && self.dirty) || must_redraw_shortcuts { - let mut children_maps = self.children[self.cursor_pos].shortcuts(context); - children_maps.extend_shortcuts(self.shortcuts(context)); - if children_maps.is_empty() { - return; - } - if let Some(i) = children_maps - .get_index_of(Shortcuts::GENERAL) - .filter(|i| i + 1 != children_maps.len()) - { - children_maps.move_index(i, children_maps.len().saturating_sub(1)); - } - if (children_maps == self.help_curr_views) && must_redraw_shortcuts { - let dialog_area = align_area( - area, - /* add box perimeter padding */ - pos_inc(self.help_content.size(), (2, 2)), - /* vertical */ - Alignment::Center, - /* horizontal */ - Alignment::Center, - ); - context.dirty_areas.push_back(dialog_area); - grid.clear_area(dialog_area, self.theme_default); - let inner_area = create_box(grid, dialog_area); - let (x, y) = grid.write_string( - "shortcuts", - 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), - ), - None, - ); - grid.write_string( - &format!( - "Press {} to close", - children_maps[Shortcuts::GENERAL]["toggle_help"] - ), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs | Attr::ITALICS, - ((x + 2, y), bottom_right!(dialog_area)), - None, - ); - let (width, height) = self.help_content.size(); - let (cols, rows) = (width!(inner_area), height!(inner_area)); + /* + let mut children_maps = self.children[self.cursor_pos].shortcuts(context); + children_maps.extend_shortcuts(self.shortcuts(context)); + if children_maps.is_empty() { + return; + } + if let Some(i) = children_maps + .get_index_of(Shortcuts::GENERAL) + .filter(|i| i + 1 != children_maps.len()) + { + children_maps.move_index(i, children_maps.len().saturating_sub(1)); + } + if (children_maps == self.help_curr_views) && must_redraw_shortcuts { + let dialog_area = area.align_inside( + /* add box perimeter padding */ + pos_inc(self.help_content.size(), (2, 2)), + /* vertical */ + Alignment::Center, + /* horizontal */ + Alignment::Center, + ); + context.dirty_areas.push_back(dialog_area); + grid.clear_area(dialog_area, self.theme_default); + let inner_area = create_box(grid, dialog_area); + let (x, y) = grid.write_string( + "shortcuts", + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::BOLD, + ( + pos_inc(dialog_area.upper_left(), (2, 0)), + dialog_area.bottom_right(), + ), + None, + ); + grid.write_string( + &format!( + "Press {} to close", + children_maps[Shortcuts::GENERAL]["toggle_help"] + ), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::ITALICS, + ((x + 2, y), dialog_area.bottom_right()), + None, + ); + let (width, height) = self.help_content.size(); + let (cols, rows) = (width!(inner_area), height!(inner_area)); - grid.copy_area( - &self.help_content, - inner_area, - ( - ( - std::cmp::min( - (width - 1).saturating_sub(cols), - self.help_screen_cursor.0, - ), - std::cmp::min( - (height - 1).saturating_sub(rows), - self.help_screen_cursor.1, - ), - ), - ( - std::cmp::min(self.help_screen_cursor.0 + cols, width - 1), - std::cmp::min(self.help_screen_cursor.1 + rows, height - 1), - ), - ), - ); - if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::Update { - id: self.id, - context: ScrollContext { - shown_lines: std::cmp::min( - (height).saturating_sub(rows + 1), - self.help_screen_cursor.1, - ) + rows, - total_lines: height, - has_more_lines: false, - }, - }, - ))); - ScrollBar::default().set_show_arrows(true).draw( - grid, - ( - pos_inc( - upper_left!(inner_area), - (width!(inner_area).saturating_sub(1), 0), - ), - bottom_right!(inner_area), - ), - context, - /* position */ - std::cmp::min((height).saturating_sub(rows + 1), self.help_screen_cursor.1), - /* visible_rows */ - rows, - /* length */ - height, - ); - } else { - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::End(self.id), - ))); - } - self.dirty = false; - return; - } - let mut max_length = 6; - let mut max_width = - "Press XXXX to close, use COMMAND \"search\" to find shortcuts".len() + 3; + grid.copy_area( + &self.help_content, + inner_area, + ( + ( + std::cmp::min( + (width - 1).saturating_sub(cols), + self.help_screen_cursor.0, + ), + std::cmp::min( + (height - 1).saturating_sub(rows), + self.help_screen_cursor.1, + ), + ), + ( + std::cmp::min(self.help_screen_cursor.0 + cols, width - 1), + std::cmp::min(self.help_screen_cursor.1 + rows, height - 1), + ), + ), + ); + if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::Update { + id: self.id, + context: ScrollContext { + shown_lines: std::cmp::min( + (height).saturating_sub(rows + 1), + self.help_screen_cursor.1, + ) + rows, + total_lines: height, + has_more_lines: false, + }, + }, + ))); + ScrollBar::default().set_show_arrows(true).draw( + grid, + ( + pos_inc( + inner_area.upper_left(), + (width!(inner_area).saturating_sub(1), 0), + ), + inner_area.bottom_right(), + ), + context, + /* position */ + std::cmp::min((height).saturating_sub(rows + 1), self.help_screen_cursor.1), + /* visible_rows */ + rows, + /* length */ + height, + ); + } else { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::End(self.id), + ))); + } + self.dirty = false; + return; + } + let mut max_length = 6; + let mut max_width = + "Press XXXX to close, use COMMAND \"search\" to find shortcuts".len() + 3; - let mut max_first_column_width = 3; + let mut max_first_column_width = 3; - for (desc, shortcuts) in children_maps.iter() { - max_length += shortcuts.len() + 3; - max_width = std::cmp::max( - max_width, - std::cmp::max( - desc.len(), - shortcuts - .values() - .map(|v| v.to_string().len() + 5) - .max() - .unwrap_or(0), - ), - ); - max_first_column_width = std::cmp::max( - max_first_column_width, - shortcuts - .values() - .map(|v| v.to_string().len() + 5) - .max() - .unwrap_or(0), - ); - } - self.help_content = - CellBuffer::new_with_context(max_width, max_length + 2, None, context); - self.help_content.set_growable(true); - self.help_content.write_string( - "use COMMAND \"search\" to find shortcuts", - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((2, 1), (max_width.saturating_sub(2), max_length - 1)), - None, - ); - let mut idx = 2; - for (desc, shortcuts) in children_maps.iter() { - self.help_content.write_string( - desc, - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)), - None, - ); - idx += 2; - for (k, v) in shortcuts { - let (x, y) = self.help_content.write_string( - &format!( - "{: >width$}", - format!("{}", v), - width = max_first_column_width - ), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs | Attr::BOLD, - ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)), - None, - ); - self.help_content.write_string( - k, - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs, - ((x + 2, y), (max_width.saturating_sub(2), max_length - 1)), - None, - ); - idx += 1; - } - idx += 1; - } - self.help_curr_views = children_maps; - let dialog_area = align_area( - area, - /* add box perimeter padding */ - pos_inc(self.help_content.size(), (2, 2)), - /* vertical */ - Alignment::Center, - /* horizontal */ - Alignment::Center, - ); - context.dirty_areas.push_back(dialog_area); - grid.clear_area(dialog_area, self.theme_default); - let inner_area = create_box(grid, dialog_area); - let (x, y) = grid.write_string( - "shortcuts", - 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), - ), - None, - ); - grid.write_string( - &format!( - "Press {} to close", - self.help_curr_views[Shortcuts::GENERAL]["toggle_help"] - ), - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs | Attr::ITALICS, - ((x + 2, y), bottom_right!(dialog_area)), - None, - ); - let (width, height) = self.help_content.size(); - let (cols, rows) = (width!(inner_area), height!(inner_area)); - if let Some(ref mut search) = self.help_search { - use crate::melib::text_processing::search::KMP; - search.positions = self - .help_content - .kmp_search(&search.pattern) - .into_iter() - .map(|offset| (offset / width, offset % width)) - .collect::>(); - 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() { - for c in self - .help_content - .row_iter(*x..*x + search.pattern.grapheme_len(), *y) - { - if i == search.cursor { - self.help_content[c] - .set_fg(results_current_attr.fg) - .set_bg(results_current_attr.bg) - .set_attrs(results_current_attr.attrs); + for (desc, shortcuts) in children_maps.iter() { + max_length += shortcuts.len() + 3; + max_width = std::cmp::max( + max_width, + std::cmp::max( + desc.len(), + shortcuts + .values() + .map(|v| v.to_string().len() + 5) + .max() + .unwrap_or(0), + ), + ); + max_first_column_width = std::cmp::max( + max_first_column_width, + shortcuts + .values() + .map(|v| v.to_string().len() + 5) + .max() + .unwrap_or(0), + ); + } + //self.help_content_size = (max_width, max_length + 2); + self.help_content = + CellBuffer::new_with_context(max_width, max_length + 2, None, context); + self.help_content.set_growable(true); + self.help_content.write_string( + "use COMMAND \"search\" to find shortcuts", + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + ((2, 1), (max_width.saturating_sub(2), max_length - 1)), + None, + ); + let mut idx = 2; + for (desc, shortcuts) in children_maps.iter() { + self.help_content.write_string( + desc, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)), + None, + ); + idx += 2; + for (k, v) in shortcuts { + let (x, y) = self.help_content.write_string( + &format!( + "{: >width$}", + format!("{}", v), + width = max_first_column_width + ), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::BOLD, + ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)), + None, + ); + self.help_content.write_string( + k, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + ((x + 2, y), (max_width.saturating_sub(2), max_length - 1)), + None, + ); + idx += 1; + } + idx += 1; + } + self.help_curr_views = children_maps; + let dialog_area = area.align_inside( + area, + /* add box perimeter padding */ + pos_inc(self.help_content.size(), (2, 2)), + /* vertical */ + Alignment::Center, + /* horizontal */ + Alignment::Center, + ); + context.dirty_areas.push_back(dialog_area); + grid.clear_area(dialog_area, self.theme_default); + let inner_area = create_box(grid, dialog_area); + let (x, y) = grid.write_string( + "shortcuts", + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::BOLD, + ( + pos_inc(dialog_area.upper_left(), (2, 0)), + dialog_area.bottom_right(), + ), + None, + ); + grid.write_string( + &format!( + "Press {} to close", + self.help_curr_views[Shortcuts::GENERAL]["toggle_help"] + ), + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::ITALICS, + ((x + 2, y), dialog_area.bottom_right()), + None, + ); + let (width, height) = self.help_content.size(); + let (cols, rows) = (width!(inner_area), height!(inner_area)); + if let Some(ref mut search) = self.help_search { + use crate::melib::text_processing::search::KMP; + search.positions = self + .help_content + .kmp_search(&search.pattern) + .into_iter() + .map(|offset| (offset / width, offset % width)) + .collect::>(); + 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() { + for c in self + .help_content + .row_iter(area, *x..*x + search.pattern.grapheme_len(), *y) + { + if i == search.cursor { + self.help_content[c] + .set_fg(results_current_attr.fg) + .set_bg(results_current_attr.bg) + .set_attrs(results_current_attr.attrs); + } else { + self.help_content[c] + .set_fg(results_attr.fg) + .set_bg(results_attr.bg) + .set_attrs(results_attr.attrs); + } + } + } + if !search.positions.is_empty() { + if let Some(mvm) = search.movement.take() { + match mvm { + PageMovement::Home => { + if self.help_screen_cursor.1 > search.positions[search.cursor].0 { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + if self.help_screen_cursor.1 + rows + < search.positions[search.cursor].0 + { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + PageMovement::Up(_) => { + if self.help_screen_cursor.1 > search.positions[search.cursor].0 { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + PageMovement::Down(_) => { + if self.help_screen_cursor.1 + rows + < search.positions[search.cursor].0 + { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + _ => {} + } + } + } + } + /* trim cursor if it's bigger than the help screen */ + self.help_screen_cursor = ( + std::cmp::min((width).saturating_sub(cols), self.help_screen_cursor.0), + std::cmp::min((height).saturating_sub(rows), self.help_screen_cursor.1), + ); + if cols == 0 || rows == 0 { + return; + } + + /* In this case we will be scrolling, so show the user how to do it */ + if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { + self.help_content.write_string( + "Use Up, Down, Left, Right to scroll.", + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::ITALICS, + ((2, 2), (max_width.saturating_sub(2), max_length - 1)), + None, + ); + } + + grid.copy_area( + &self.help_content, + inner_area, + ( + ( + std::cmp::min((width - 1).saturating_sub(cols), self.help_screen_cursor.0), + std::cmp::min((height - 1).saturating_sub(rows), self.help_screen_cursor.1), + ), + ( + std::cmp::min(self.help_screen_cursor.0 + cols, width - 1), + std::cmp::min(self.help_screen_cursor.1 + rows, height - 1), + ), + ), + ); + if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::Update { + id: self.id, + context: ScrollContext { + shown_lines: std::cmp::min( + (height).saturating_sub(rows), + self.help_screen_cursor.1, + ) + rows, + total_lines: height, + has_more_lines: false, + }, + }, + ))); + ScrollBar::default().set_show_arrows(true).draw( + grid, + inner_area.skip_cols(inner_area.width().saturating_sub(1)), + context, + /* position */ + std::cmp::min((height).saturating_sub(rows), self.help_screen_cursor.1), + /* visible_rows */ + rows, + /* length */ + height, + ); } else { - self.help_content[c] - .set_fg(results_attr.fg) - .set_bg(results_attr.bg) - .set_attrs(results_attr.attrs); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( + ScrollUpdate::End(self.id), + ))); } - } - } - if !search.positions.is_empty() { - if let Some(mvm) = search.movement.take() { - match mvm { - PageMovement::Home => { - if self.help_screen_cursor.1 > search.positions[search.cursor].0 { - self.help_screen_cursor.1 = search.positions[search.cursor].0; - } - if self.help_screen_cursor.1 + rows - < search.positions[search.cursor].0 - { - self.help_screen_cursor.1 = search.positions[search.cursor].0; - } - } - PageMovement::Up(_) => { - if self.help_screen_cursor.1 > search.positions[search.cursor].0 { - self.help_screen_cursor.1 = search.positions[search.cursor].0; - } - } - PageMovement::Down(_) => { - if self.help_screen_cursor.1 + rows - < search.positions[search.cursor].0 - { - self.help_screen_cursor.1 = search.positions[search.cursor].0; - } - } - _ => {} - } - } - } - } - /* trim cursor if it's bigger than the help screen */ - self.help_screen_cursor = ( - std::cmp::min((width).saturating_sub(cols), self.help_screen_cursor.0), - std::cmp::min((height).saturating_sub(rows), self.help_screen_cursor.1), - ); - if cols == 0 || rows == 0 { - return; - } - - /* In this case we will be scrolling, so show the user how to do it */ - if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { - self.help_content.write_string( - "Use Up, Down, Left, Right to scroll.", - self.theme_default.fg, - self.theme_default.bg, - self.theme_default.attrs | Attr::ITALICS, - ((2, 2), (max_width.saturating_sub(2), max_length - 1)), - None, - ); - } - - grid.copy_area( - &self.help_content, - inner_area, - ( - ( - std::cmp::min((width - 1).saturating_sub(cols), self.help_screen_cursor.0), - std::cmp::min((height - 1).saturating_sub(rows), self.help_screen_cursor.1), - ), - ( - std::cmp::min(self.help_screen_cursor.0 + cols, width - 1), - std::cmp::min(self.help_screen_cursor.1 + rows, height - 1), - ), - ), - ); - if height.wrapping_div(rows + 1) > 0 || width.wrapping_div(cols + 1) > 0 { - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::Update { - id: self.id, - context: ScrollContext { - shown_lines: std::cmp::min( - (height).saturating_sub(rows), - self.help_screen_cursor.1, - ) + rows, - total_lines: height, - has_more_lines: false, - }, - }, - ))); - ScrollBar::default().set_show_arrows(true).draw( - grid, - ( - pos_inc( - upper_left!(inner_area), - (width!(inner_area).saturating_sub(1), 0), - ), - bottom_right!(inner_area), - ), - context, - /* position */ - std::cmp::min((height).saturating_sub(rows), self.help_screen_cursor.1), - /* visible_rows */ - rows, - /* length */ - height, - ); - } else { - context - .replies - .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( - ScrollUpdate::End(self.id), - ))); - } + */ } self.dirty = false; } @@ -1600,6 +1617,7 @@ impl Component for Tabbed { } } +/* #[derive(Debug, Clone, PartialEq, Eq)] pub struct RawBuffer { pub buf: CellBuffer, @@ -1619,7 +1637,7 @@ impl Component for RawBuffer { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if self.dirty { let (width, height) = self.buf.size(); - let (cols, rows) = (width!(area), height!(area)); + let (cols, rows) = (area.width(), area.height()); self.cursor = ( std::cmp::min(width.saturating_sub(cols), self.cursor.0), std::cmp::min(height.saturating_sub(rows), self.cursor.1), @@ -1697,3 +1715,4 @@ impl RawBuffer { self.title.as_deref().unwrap_or("untitled") } } +*/ diff --git a/meli/src/utilities/dialogs.rs b/meli/src/utilities/dialogs.rs index 9a5c5004..750e8496 100644 --- a/meli/src/utilities/dialogs.rs +++ b/meli/src/utilities/dialogs.rs @@ -53,7 +53,6 @@ pub struct Selector< single_only: bool, entries: Vec<(T, bool)>, entry_titles: Vec, - pub content: CellBuffer, theme_default: ThemeAttribute, cursor: SelectorCursor, @@ -114,7 +113,6 @@ impl 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 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 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 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 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 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 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 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 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 Selector { + ) -> Self { let entry_titles = entries .iter_mut() .map(|(_id, ref mut title)| std::mem::take(title)) @@ -773,11 +458,10 @@ impl bool { @@ -871,18 +501,17 @@ impl)>, + filtered_content: Option<(String, Result)>, text_lines: Vec, line_breaker: LineBreakText, movement: Option, @@ -180,7 +181,7 @@ impl Pager { } pub fn filter(&mut self, cmd: &str) { - let _f = |bin: &str, text: &str| -> Result { + let _f = |bin: &str, text: &str| -> Result { 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); } diff --git a/meli/src/utilities/tables.rs b/meli/src/utilities/tables.rs index ecb26a6a..8c3a2725 100644 --- a/meli/src/utilities/tables.rs +++ b/meli/src/utilities/tables.rs @@ -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 { pub cursor_config: TableCursorConfig, pub theme_config: TableThemeConfig, - pub columns: Box<[CellBuffer; N]>, + pub columns: Box<[Screen; 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 Default for DataColumns { let ptr = &data as *const [MaybeUninit; 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 DataColumns { 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 DataColumns { 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 DataColumns { 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 DataColumns { } 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 DataColumns { 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); } } } diff --git a/meli/src/utilities/text.rs b/meli/src/utilities/text.rs index e13e7721..0c2bf61d 100644 --- a/meli/src/utilities/text.rs +++ b/meli/src/utilities/text.rs @@ -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 * ########################################## diff --git a/meli/src/utilities/widgets.rs b/meli/src/utilities/widgets.rs index 88b8c70b..b35d868c 100644 --- a/meli/src/utilities/widgets.rs +++ b/meli/src/utilities/widgets.rs @@ -347,19 +347,10 @@ impl FormWidget impl Component for FormWidget { 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 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 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 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 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, - 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) -> Box { 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); diff --git a/tools/src/embed.rs b/tools/src/embed.rs index ce5964f2..0385da00 100644 --- a/tools/src/embed.rs +++ b/tools/src/embed.rs @@ -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) => {