Re-add contact list and editor support

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/312/head
Manos Pitsidianakis 7 months ago
parent ba7a97e90b
commit 54d21f25fd
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -24,8 +24,8 @@ use std::collections::HashMap;
use melib::Card; use melib::Card;
use crate::{ use crate::{
terminal::*, Action::*, CellBuffer, Component, ComponentId, Context, Field, FormWidget, Key, terminal::*, CellBuffer, Component, ComponentId, Context, Field, FormWidget, Key, StatusEvent,
StatusEvent, TabAction, ThemeAttribute, UIDialog, UIEvent, ThemeAttribute, UIDialog, UIEvent,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -33,7 +33,6 @@ enum ViewMode {
ReadOnly, ReadOnly,
Discard(Box<UIDialog<char>>), Discard(Box<UIDialog<char>>),
Edit, Edit,
//New,
} }
#[derive(Debug)] #[derive(Debug)]
@ -44,11 +43,10 @@ pub struct ContactManager {
mode: ViewMode, mode: ViewMode,
form: FormWidget<bool>, form: FormWidget<bool>,
pub account_pos: usize, pub account_pos: usize,
content: CellBuffer, content: Screen<Virtual>,
theme_default: ThemeAttribute, theme_default: ThemeAttribute,
dirty: bool, dirty: bool,
has_changes: bool, has_changes: bool,
initialized: bool, initialized: bool,
} }
@ -68,7 +66,7 @@ impl ContactManager {
mode: ViewMode::Edit, mode: ViewMode::Edit,
form: FormWidget::default(), form: FormWidget::default(),
account_pos: 0, account_pos: 0,
content: CellBuffer::new_with_context(100, 1, None, context), content: Screen::<Virtual>::new(),
theme_default, theme_default,
dirty: true, dirty: true,
has_changes: false, has_changes: false,
@ -77,34 +75,38 @@ impl ContactManager {
} }
fn initialize(&mut self, context: &Context) { fn initialize(&mut self, context: &Context) {
let (width, _) = self.content.size(); if !self.content.resize_with_context(100, 1, context) {
return;
}
let mut area = self.content.area();
let (x, _) = self.content.write_string( let (x, _) = self.content.grid_mut().write_string(
"Last edited: ", "Last edited: ",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((0, 0), (width - 1, 0)), area,
None, None,
); );
let (x, y) = self.content.write_string( area = area.skip_cols(x);
let (x, y) = self.content.grid_mut().write_string(
&self.card.last_edited(), &self.card.last_edited(),
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x, 0), (width - 1, 0)), area,
None, None,
); );
area = area.skip(x, y);
if self.card.external_resource() { if self.card.external_resource() {
self.mode = ViewMode::ReadOnly; self.mode = ViewMode::ReadOnly;
_ = self.content.resize(self.content.size().0, 2, None); self.content.grid_mut().write_string(
self.content.write_string(
"This contact's origin is external and cannot be edited within meli.", "This contact's origin is external and cannot be edited within meli.",
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,
self.theme_default.attrs, self.theme_default.attrs,
((x, y), (width - 1, y)), area,
None, None,
); );
} }
@ -147,23 +149,15 @@ impl Component for ContactManager {
self.initialized = true; self.initialized = true;
} }
let upper_left = area.upper_left(); if self.is_dirty() {
let bottom_right = area.bottom_right(); grid.clear_area(area, self.theme_default);
grid.copy_area(self.content.grid(), area.skip_rows(2), self.content.area());
if self.dirty {
let (width, _height) = self.content.size();
grid.clear_area(
(upper_left, set_y(bottom_right, get_y(upper_left) + 1)),
self.theme_default,
);
grid.copy_area(&self.content, area, ((0, 0), (width - 1, 0)));
self.dirty = false; self.dirty = false;
} }
self.form.draw( self.form.draw(
grid, grid,
(set_y(upper_left, get_y(upper_left) + 2), bottom_right), area.skip_rows(2 + self.content.area().height()),
context, context,
); );
if let ViewMode::Discard(ref mut selector) = self.mode { if let ViewMode::Discard(ref mut selector) = self.mode {
@ -177,23 +171,27 @@ impl Component for ContactManager {
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
if let UIEvent::ConfigReload { old_settings: _ } = event { if let UIEvent::ConfigReload { old_settings: _ } = event {
self.theme_default = crate::conf::value(context, "theme_default"); self.theme_default = crate::conf::value(context, "theme_default");
self.content = CellBuffer::new_with_context(100, 1, None, context); self.content.grid_mut().empty();
self.initialized = false; self.initialized = false;
self.set_dirty(true); self.set_dirty(true);
} }
match self.mode { match self.mode {
ViewMode::Discard(ref mut selector) => { ViewMode::Discard(ref mut selector) => {
if matches!(event, UIEvent::ComponentUnrealize(ref id) if *id == selector.id()) {
selector.unrealize(context);
self.mode = ViewMode::Edit;
self.set_dirty(true);
return true;
}
if selector.process_event(event, context) { if selector.process_event(event, context) {
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
} }
ViewMode::Edit => { ViewMode::Edit => {
if let (Some(parent_id), &UIEvent::Input(Key::Esc)) = (self.parent_id, &event) { if matches!(event, UIEvent::Input(Key::Esc)) {
if self.can_quit_cleanly(context) { if self.can_quit_cleanly(context) {
context self.unrealize(context);
.replies
.push_back(UIEvent::Action(Tab(TabAction::Kill(parent_id))));
} }
return true; return true;
} }
@ -250,11 +248,7 @@ impl Component for ContactManager {
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty self.dirty
|| self.form.is_dirty() || self.form.is_dirty()
|| if let ViewMode::Discard(ref selector) = self.mode { || matches!(self.mode, ViewMode::Discard(ref selector) if selector.is_dirty())
selector.is_dirty()
} else {
false
}
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
@ -274,28 +268,31 @@ impl Component for ContactManager {
return true; return true;
} }
if let Some(parent_id) = self.parent_id { if matches!(self.mode, ViewMode::Discard(_)) {
true
} else {
let Some(parent_id) = self.parent_id else {
return true;
};
/* Play it safe and ask user for confirmation */ /* Play it safe and ask user for confirmation */
self.mode = ViewMode::Discard(Box::new(UIDialog::new( self.mode = ViewMode::Discard(Box::new(UIDialog::new(
"this contact has unsaved changes", "this contact has unsaved changes",
vec![ vec![
('x', "quit without saving".to_string()), ('y', "quit without saving".to_string()),
('y', "save draft and quit".to_string()),
('n', "cancel".to_string()), ('n', "cancel".to_string()),
], ],
true, true,
Some(Box::new(move |_, results: &[char]| match results[0] { Some(Box::new(move |id, results: &[char]| {
'x' => Some(UIEvent::Action(Tab(TabAction::Kill(parent_id)))), if matches!(results.first(), Some(&'y')) {
'n' => None, Some(UIEvent::ComponentUnrealize(parent_id))
'y' => None, } else {
_ => None, Some(UIEvent::ComponentUnrealize(id))
}
})), })),
context, context,
))); )));
self.set_dirty(true); self.set_dirty(true);
false false
} else {
true
} }
} }
} }

@ -24,15 +24,15 @@ use std::cmp;
use melib::{backends::AccountHash, text_processing::TextProcessing, Card, CardId, Draft}; use melib::{backends::AccountHash, text_processing::TextProcessing, Card, CardId, Draft};
use crate::{ use crate::{
conf, /* contacts::editor::ContactManager, */ shortcut, terminal::*, Action::Tab, conf, contacts::editor::ContactManager, shortcut, terminal::*, Action::Tab, Component,
Component, ComponentId, Composer, Context, DataColumns, PageMovement, ScrollContext, ComponentId, Composer, Context, DataColumns, PageMovement, ScrollContext, ScrollUpdate,
ScrollUpdate, ShortcutMaps, Shortcuts, StatusEvent, TabAction, ThemeAttribute, UIEvent, UIMode, ShortcutMaps, Shortcuts, StatusEvent, TabAction, ThemeAttribute, UIEvent, UIMode,
}; };
#[derive(Debug, PartialEq, Eq)] #[derive(Debug)]
enum ViewMode { enum ViewMode {
List, List,
View(ComponentId), View(Box<ContactManager>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -66,7 +66,6 @@ pub struct ContactList {
menu_visibility: bool, menu_visibility: bool,
movement: Option<PageMovement>, movement: Option<PageMovement>,
cmd_buf: String, cmd_buf: String,
//view: Option<ContactManager>,
ratio: usize, // right/(container width) * 100 ratio: usize, // right/(container width) * 100
id: ComponentId, id: ComponentId,
} }
@ -104,7 +103,6 @@ impl ContactList {
dirty: true, dirty: true,
movement: None, movement: None,
cmd_buf: String::with_capacity(8), cmd_buf: String::with_capacity(8),
//view: None,
ratio: 90, ratio: 90,
sidebar_divider: context.settings.listing.sidebar_divider, sidebar_divider: context.settings.listing.sidebar_divider,
sidebar_divider_theme: conf::value(context, "mail.sidebar_divider"), sidebar_divider_theme: conf::value(context, "mail.sidebar_divider"),
@ -323,9 +321,11 @@ impl ContactList {
} }
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let total_area = area;
/* reserve top row for column headers */
let header_area = area.nth_row(0);
let area = area.skip_rows(1);
if self.length == 0 { if self.length == 0 {
/* reserve top row for column headers */
let area = area.skip_rows(1);
grid.clear_area(area, self.theme_default); grid.clear_area(area, self.theme_default);
grid.copy_area( grid.copy_area(
@ -333,7 +333,7 @@ impl ContactList {
area, area,
self.data_columns.columns[0].area(), self.data_columns.columns[0].area(),
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(total_area);
return; return;
} }
let rows = area.height(); let rows = area.height();
@ -385,7 +385,7 @@ impl ContactList {
ScrollUpdate::Update { ScrollUpdate::Update {
id: self.id, id: self.id,
context: ScrollContext { context: ScrollContext {
shown_lines: top_idx + rows, shown_lines: (top_idx + rows).min(self.length - top_idx),
total_lines: self.length, total_lines: self.length,
has_more_lines: false, has_more_lines: false,
}, },
@ -408,7 +408,7 @@ impl ContactList {
if *idx >= self.length { if *idx >= self.length {
continue; continue;
} }
let new_area = area.nth_row(1 + *idx % rows); let new_area = area.nth_row(*idx % rows);
self.highlight_line(grid, new_area, *idx); self.highlight_line(grid, new_area, *idx);
context.dirty_areas.push_back(new_area); context.dirty_areas.push_back(new_area);
} }
@ -422,17 +422,11 @@ impl ContactList {
} }
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
grid.clear_area(area, self.theme_default); grid.clear_area(total_area, self.theme_default);
_ = self _ = self.data_columns.recalc_widths(area.size(), top_idx);
.data_columns
.recalc_widths((area.width(), area.height().saturating_sub(1)), top_idx);
/* copy table columns */ /* copy table columns */
self.data_columns.draw( self.data_columns
grid, .draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area));
top_idx,
self.cursor_pos,
grid.bounds_iter(area.skip_rows(1)),
);
let header_attrs = crate::conf::value(context, "widgets.list.header"); let header_attrs = crate::conf::value(context, "widgets.list.header");
let mut x = 0; let mut x = 0;
@ -451,18 +445,19 @@ impl ContactList {
header_attrs.fg, header_attrs.fg,
header_attrs.bg, header_attrs.bg,
header_attrs.attrs, header_attrs.attrs,
area.skip_cols(x) header_area
.skip_cols(x)
.take_cols(x + (self.data_columns.widths[i])), .take_cols(x + (self.data_columns.widths[i])),
None, None,
); );
x += self.data_columns.widths[i] + 2; // + SEPARATOR x += self.data_columns.widths[i] + 2; // + SEPARATOR
if x > area.width() { if x > header_area.width() {
break; break;
} }
} }
grid.change_theme(area.nth_row(0), header_attrs); grid.change_theme(header_area, header_attrs);
if top_idx + rows > self.length { if top_idx + rows > self.length {
grid.clear_area( grid.clear_area(
@ -470,21 +465,17 @@ impl ContactList {
self.theme_default, self.theme_default,
); );
} }
self.highlight_line( self.highlight_line(grid, area.nth_row(self.cursor_pos % rows), self.cursor_pos);
grid, context.dirty_areas.push_back(total_area);
area.nth_row(1 + self.cursor_pos % rows),
self.cursor_pos,
);
context.dirty_areas.push_back(area);
} }
} }
impl Component for ContactList { impl Component for ContactList {
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 let Some(mgr) = self.view.as_mut() { if let ViewMode::View(ref mut mgr) = self.mode {
// mgr.draw(grid, area, context); mgr.draw(grid, area, context);
// return; return;
//} }
if !self.dirty { if !self.dirty {
return; return;
@ -531,7 +522,6 @@ impl Component for ContactList {
UIEvent::VisibilityChange(true) => { UIEvent::VisibilityChange(true) => {
self.initialized = false; self.initialized = false;
self.set_dirty(true); self.set_dirty(true);
return true;
} }
UIEvent::ConfigReload { old_settings: _ } => { UIEvent::ConfigReload { old_settings: _ } => {
self.theme_default = crate::conf::value(context, "theme_default"); self.theme_default = crate::conf::value(context, "theme_default");
@ -544,12 +534,6 @@ impl Component for ContactList {
self.initialized = false; self.initialized = false;
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::ComponentUnrealize(ref kill_id) if self.mode == ViewMode::View(*kill_id) => {
self.mode = ViewMode::List;
//self.view.take();
self.set_dirty(true);
return true;
}
UIEvent::ChangeMode(UIMode::Normal) => { UIEvent::ChangeMode(UIMode::Normal) => {
self.set_dirty(true); self.set_dirty(true);
} }
@ -559,314 +543,315 @@ impl Component for ContactList {
_ => {} _ => {}
} }
//if let Some(ref mut v) = self.view { if let ViewMode::View(ref mut mgr) = self.mode {
// if v.process_event(event, context) { if matches!(event, UIEvent::ComponentUnrealize(id) if *id == mgr.id()) {
// return true; mgr.unrealize(context);
// } self.mode = ViewMode::List;
//} self.set_dirty(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;
self.mode = ViewMode::View(manager.id());
self.view = Some(manager);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
ScrollUpdate::End(self.id),
)));
*/
return true; return true;
} }
if mgr.process_event(event, context) {
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),
)));
*/
return true; 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,
))))));
return true; let shortcuts = self.shortcuts(context);
} if matches!(self.mode, ViewMode::List) {
UIEvent::Input(ref key) match *event {
if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["delete_contact"]) => UIEvent::Input(ref key)
{ if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["create_contact"]) =>
if self.length == 0 { {
let mut manager = Box::new(ContactManager::new(context));
manager.set_parent_id(self.id);
manager.account_pos = self.account_pos;
self.mode = ViewMode::View(manager);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
ScrollUpdate::End(self.id),
)));
return true; 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);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true; UIEvent::Input(ref key)
} if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["edit_contact"]) =>
UIEvent::Input(ref key) {
if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["next_account"]) => if self.length == 0 {
{ return true;
let amount = if self.cmd_buf.is_empty() { }
1 let account = &mut context.accounts[self.account_pos];
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() { let book = &mut account.address_book;
self.cmd_buf.clear(); let card = book[&self.id_positions[self.cursor_pos]].clone();
let mut manager = Box::new(ContactManager::new(context));
manager.set_parent_id(self.id);
manager.card = card;
manager.account_pos = self.account_pos;
self.mode = ViewMode::View(manager);
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
amount ScrollUpdate::End(self.id),
} else { )));
self.cmd_buf.clear(); 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 context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::Action(Tab(TabAction::New(Some(Box::new(
composer,
))))));
return true; return true;
}; }
if self.account_pos + amount < self.accounts.len() { UIEvent::Input(ref key)
self.account_pos += amount; if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["delete_contact"]) =>
self.set_dirty(true); {
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.initialized = false;
self.cursor_pos = 0; self.set_dirty(true);
self.new_cursor_pos = 0;
self.length = 0;
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
self.status(context),
))); 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::<usize>() {
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::<usize>() {
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; return true;
}; }
if self.accounts.is_empty() { 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::<usize>() {
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; return true;
} }
if self.account_pos >= amount { UIEvent::Input(ref k)
self.account_pos -= amount; if shortcut!(
k == shortcuts[Shortcuts::CONTACT_LIST]["toggle_menu_visibility"]
) =>
{
self.menu_visibility = !self.menu_visibility;
self.set_dirty(true); 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(Key::Esc) | UIEvent::Input(Key::Alt(''))
} if !self.cmd_buf.is_empty() =>
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::<usize>() {
self.cmd_buf.clear(); self.cmd_buf.clear();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount return true;
} else { }
self.cmd_buf.clear(); UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
self.cmd_buf.clone(),
)));
return true; return true;
}; }
self.movement = Some(PageMovement::Up(amount)); UIEvent::Input(ref key)
self.set_dirty(true); if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_up"]) =>
return true; {
} let amount = if self.cmd_buf.is_empty() {
UIEvent::Input(ref key) 1
if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_down"]) => } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
{ self.cmd_buf.clear();
if self.cursor_pos >= self.length.saturating_sub(1) { 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; return true;
} }
let amount = if self.cmd_buf.is_empty() { UIEvent::Input(ref key)
1 if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["scroll_down"]) =>
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() { {
self.cmd_buf.clear(); if self.cursor_pos >= self.length.saturating_sub(1) {
context return true;
.replies }
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); let amount = if self.cmd_buf.is_empty() {
amount 1
} else { } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear(); self.cmd_buf.clear();
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .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; return true;
}; }
self.set_dirty(true); UIEvent::Input(ref key)
self.movement = Some(PageMovement::Down(amount)); if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) =>
return true; {
} let mult = if self.cmd_buf.is_empty() {
UIEvent::Input(ref key) 1
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) => } else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
{ self.cmd_buf.clear();
let mult = if self.cmd_buf.is_empty() { context
1 .replies
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
self.cmd_buf.clear(); mult
context } else {
.replies self.cmd_buf.clear();
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); context
mult .replies
} else { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
self.cmd_buf.clear(); return true;
context };
.replies self.set_dirty(true);
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); self.movement = Some(PageMovement::PageUp(mult));
return true; return true;
}; }
self.set_dirty(true); UIEvent::Input(ref key)
self.movement = Some(PageMovement::PageUp(mult)); if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) =>
return true; {
} let mult = if self.cmd_buf.is_empty() {
UIEvent::Input(ref key) 1
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) => } else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
{ self.cmd_buf.clear();
let mult = if self.cmd_buf.is_empty() { context
1 .replies
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
self.cmd_buf.clear(); mult
context } else {
.replies self.cmd_buf.clear();
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); context
mult .replies
} else { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
self.cmd_buf.clear(); return true;
context };
.replies self.set_dirty(true);
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); self.movement = Some(PageMovement::PageDown(mult));
return true; return true;
}; }
self.set_dirty(true); UIEvent::Input(ref key)
self.movement = Some(PageMovement::PageDown(mult)); if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) =>
return true; {
} self.set_dirty(true);
UIEvent::Input(ref key) self.movement = Some(PageMovement::Home);
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) => return true;
{ }
self.set_dirty(true); UIEvent::Input(ref key)
self.movement = Some(PageMovement::Home); if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) =>
return true; {
} self.set_dirty(true);
UIEvent::Input(ref key) self.movement = Some(PageMovement::End);
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) => return true;
{ }
self.set_dirty(true); _ => {}
self.movement = Some(PageMovement::End);
return true;
} }
_ => {}
} }
//}
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty //|| self.view.as_ref().map(|v| self.dirty || matches!(self.mode, ViewMode::View(ref mgr) if mgr.is_dirty())
//|| v.is_dirty()).unwrap_or(false)
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
//if let Some(p) = self.view.as_mut() { if let ViewMode::View(ref mut mgr) = self.mode {
// p.set_dirty(value); mgr.set_dirty(value);
//}; }
self.dirty = value; self.dirty = value;
} }
@ -876,12 +861,13 @@ impl Component for ContactList {
.replies .replies
.push_back(UIEvent::Action(Tab(TabAction::Kill(uuid)))); .push_back(UIEvent::Action(Tab(TabAction::Kill(uuid))));
} }
fn shortcuts(&self, context: &Context) -> ShortcutMaps { fn shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = ShortcutMaps::default(); //self let mut map = if let ViewMode::View(ref mgr) = self.mode {
//.view mgr.shortcuts(context)
//.as_ref() } else {
//.map(|p| p.shortcuts(context)) ShortcutMaps::default()
//.unwrap_or_default(); };
map.insert( map.insert(
Shortcuts::CONTACT_LIST, Shortcuts::CONTACT_LIST,
@ -899,10 +885,11 @@ impl Component for ContactList {
self.id self.id
} }
fn can_quit_cleanly(&mut self, _context: &Context) -> bool { fn can_quit_cleanly(&mut self, context: &Context) -> bool {
if let ViewMode::View(ref mut mgr) = self.mode {
return mgr.can_quit_cleanly(context);
}
true true
//self.view .as_mut() .map(|p| p.can_quit_cleanly(context))
// .unwrap_or(true)
} }
fn status(&self, context: &Context) -> String { fn status(&self, context: &Context) -> String {

@ -19,6 +19,5 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod editor;
pub mod list; pub mod list;
//pub mod editor;

@ -883,152 +883,139 @@ impl Component for Composer {
self.update_form(context); self.update_form(context);
self.initialized = true; self.initialized = true;
} }
let header_height = self.form.len();
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
if self.dirty {
grid.clear_area(area, theme_default);
}
let mid = 0; let header_height = self.form.len();
/*
let mid = if width > 80 {
let width = width - 80;
let mid = width / 2;
if self.dirty {
for i in get_y(upper_left)..=get_y(bottom_right) {
//set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
grid[(mid, i)]
.set_fg(theme_default.fg)
.set_bg(theme_default.bg);
//set_and_join_box(grid, (mid + 80, i), VERT_BOUNDARY);
grid[(mid + 80, i)]
.set_fg(theme_default.fg)
.set_bg(theme_default.bg);
}
}
mid
} else {
0
};
*/
let header_area = area let header_area = area
.skip_rows(1)
.take_rows(header_height) .take_rows(header_height)
.skip_cols(mid + 1) .skip_cols(1)
.skip_cols_from_end(mid); .skip_cols_from_end(1);
let attachments_no = self.draft.attachments().len(); let attachments_no = self.draft.attachments().len();
let attachment_area = area let attachment_area = area
.skip_rows(header_height) .skip_rows(header_height + 1)
.skip_rows( .skip_rows(
area.height() area.height()
.saturating_sub(header_area.height() + 4 + attachments_no), .saturating_sub(header_area.height() + 4 + attachments_no),
) )
.skip_cols(mid + 1); .skip_cols(1)
.skip_cols_from_end(1);
let body_area = area let body_area = area
.skip_rows(header_height) .skip_rows(header_height + 2)
.skip_rows_from_end(attachment_area.height()); .skip_rows_from_end(attachment_area.height())
.skip_cols(1)
.skip_cols_from_end(1);
grid.clear_area(area.nth_row(0), crate::conf::value(context, "highlight")); if self.dirty {
grid.write_string( grid.clear_area(area.nth_row(0), crate::conf::value(context, "highlight"));
if self.reply_context.is_some() { grid.write_string(
"COMPOSING REPLY" if self.reply_context.is_some() {
} else { "COMPOSING REPLY"
"COMPOSING MESSAGE" } else {
}, "COMPOSING MESSAGE"
crate::conf::value(context, "highlight").fg, },
crate::conf::value(context, "highlight").bg, crate::conf::value(context, "highlight").fg,
crate::conf::value(context, "highlight").attrs, crate::conf::value(context, "highlight").bg,
area.nth_row(0), crate::conf::value(context, "highlight").attrs,
None, area.nth_row(0),
); None,
);
/* }
grid.change_theme(
(
set_x(pos_dec(header_area.upper_left(), (0, 1)), x),
set_y(header_area.bottom_right(), y),
),
crate::conf::value(context, "highlight"),
);
grid.clear_area( /* Regardless of view mode, do the following */
(
pos_dec(upper_left, (0, 1)),
set_x(bottom_right, get_x(upper_left) + mid),
),
theme_default,
);
grid.clear_area( if self.dirty {
( match self.cursor {
( Cursor::Headers => {
get_x(bottom_right).saturating_sub(mid), grid.change_theme(header_area, theme_default);
get_y(upper_left).saturating_sub(1), }
), Cursor::Body => {
bottom_right, grid.change_theme(
), body_area,
theme_default, ThemeAttribute {
); fg: theme_default.fg,
*/ bg: crate::conf::value(context, "highlight").bg,
attrs: if grid.use_color {
crate::conf::value(context, "highlight").attrs
} else {
crate::conf::value(context, "highlight").attrs | Attr::REVERSE
},
},
);
}
Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {}
}
}
/* Regardless of view mode, do the following */
self.form.draw(grid, header_area, context); self.form.draw(grid, header_area, context);
if let Some(ref mut embedded) = self.embedded { if let Some(ref mut embedded) = self.embedded {
let embed_pty = &mut embedded.status; let embed_pty = &mut embedded.status;
let embed_area = area; let embed_area = area;
match embed_pty { match embed_pty {
EmbedStatus::Running(_, _) => { EmbedStatus::Running(_, _) => {
let mut guard = embed_pty.lock().unwrap(); if self.dirty {
grid.clear_area(embed_area, theme_default); let mut guard = embed_pty.lock().unwrap();
grid.clear_area(embed_area, theme_default);
grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.area());
guard.set_terminal_size((embed_area.width(), embed_area.height())); grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.area());
context.dirty_areas.push_back(area); guard.set_terminal_size((embed_area.width(), embed_area.height()));
self.dirty = false; context.dirty_areas.push_back(embed_area);
self.dirty = false;
}
return; return;
} }
EmbedStatus::Stopped(_, _) => { EmbedStatus::Stopped(_, _) => {
let guard = embed_pty.lock().unwrap(); if self.dirty {
let guard = embed_pty.lock().unwrap();
grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.buffer().area());
grid.change_colors(embed_area, Color::Byte(8), theme_default.bg); grid.copy_area(guard.grid.buffer(), embed_area, guard.grid.buffer().area());
let our_map: ShortcutMap = grid.change_colors(embed_area, Color::Byte(8), theme_default.bg);
account_settings!(context[self.account_hash].shortcuts.composing) let our_map: ShortcutMap =
.key_values(); account_settings!(context[self.account_hash].shortcuts.composing)
let mut shortcuts: ShortcutMaps = Default::default(); .key_values();
shortcuts.insert(Shortcuts::COMPOSING, our_map); let mut shortcuts: ShortcutMaps = Default::default();
let stopped_message: String = shortcuts.insert(Shortcuts::COMPOSING, our_map);
format!("Process with PID {} has stopped.", guard.child_pid); let stopped_message: String =
let stopped_message_2: String = format!( format!("Process with PID {} has stopped.", guard.child_pid);
"-press '{}' (edit shortcut) to re-activate.", let stopped_message_2: String = format!(
shortcuts[Shortcuts::COMPOSING]["edit"] "-press '{}' (edit shortcut) to re-activate.",
); shortcuts[Shortcuts::COMPOSING]["edit"]
const STOPPED_MESSAGE_3: &str = );
"-press Ctrl-C to forcefully kill it and return to editor."; const STOPPED_MESSAGE_3: &str =
let max_len = std::cmp::max( "-press Ctrl-C to forcefully kill it and return to editor.";
stopped_message.len(), let max_len = std::cmp::max(
std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()), stopped_message.len(),
); std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()),
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(),
stopped_message_2.as_str(),
STOPPED_MESSAGE_3,
]
.iter()
.enumerate()
{
grid.write_string(
l,
theme_default.fg,
theme_default.bg,
theme_default.attrs,
inner_area.skip_rows(i),
None, //Some(get_x(inner_area.upper_left())),
); );
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(),
stopped_message_2.as_str(),
STOPPED_MESSAGE_3,
]
.iter()
.enumerate()
{
grid.write_string(
l,
theme_default.fg,
theme_default.bg,
theme_default.attrs,
inner_area.skip_rows(i),
None,
);
}
context.dirty_areas.push_back(area);
self.dirty = false;
} }
context.dirty_areas.push_back(area);
self.dirty = false;
return; return;
} }
} }
@ -1039,51 +1026,13 @@ impl Component for Composer {
if self.pager.size().0 > body_area.width() { if self.pager.size().0 > body_area.width() {
self.pager.set_initialised(false); self.pager.set_initialised(false);
} }
// Force clean pager area, because if body height is less than body_area it will if self.dirty {
// might leave draw artifacts in the remaining area. // Force clean pager area, because if body height is less than body_area it will
grid.clear_area(body_area, theme_default); // might leave draw artifacts in the remaining area.
self.set_dirty(true); grid.clear_area(body_area, theme_default);
self.pager.draw(grid, body_area, context); self.pager.set_dirty(true);
match self.cursor {
Cursor::Headers => {
/*
grid.change_theme(
(
pos_dec(body_area.upper_left(), (1, 0)),
pos_dec(
set_y(body_area.upper_left(), get_y(body_area.bottom_right())),
(1, 0),
),
),
theme_default,
);
*/
}
Cursor::Body => {
/*
grid.change_theme(
(
pos_dec(body_area.upper_left(), (1, 0)),
pos_dec(
set_y(body_area.upper_left(), get_y(body_area.bottom_right())),
(1, 0),
),
),
ThemeAttribute {
fg: theme_default.fg,
bg: crate::conf::value(context, "highlight").bg,
attrs: if grid.use_color {
crate::conf::value(context, "highlight").attrs
} else {
crate::conf::value(context, "highlight").attrs | Attr::REVERSE
},
},
);
*/
}
Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {}
} }
self.pager.draw(grid, body_area, context);
//if !self.mode.is_edit_attachments() { //if !self.mode.is_edit_attachments() {
self.draw_attachments(grid, attachment_area, context); self.draw_attachments(grid, attachment_area, context);
@ -1125,8 +1074,11 @@ impl Component for Composer {
s.draw(grid, body_area, context); s.draw(grid, body_area, context);
} }
} }
self.dirty = false;
context.dirty_areas.push_back(area); if self.dirty {
self.dirty = false;
context.dirty_areas.push_back(area);
}
} }
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {

@ -154,7 +154,6 @@ pub enum UIEvent {
Contacts(ContactEvent), Contacts(ContactEvent),
Compose(ComposeEvent), Compose(ComposeEvent),
FinishedUIDialog(ComponentId, UIMessage), FinishedUIDialog(ComponentId, UIMessage),
CanceledUIDialog(ComponentId),
IntraComm { IntraComm {
from: ComponentId, from: ComponentId,
to: ComponentId, to: ComponentId,

@ -21,11 +21,9 @@
use super::*; use super::*;
const OK_CANCEL: &str = "OK Cancel"; const OK: &str = "OK";
const OK_OFFSET: usize = 0; const CANCEL: &str = "Cancel";
const OK_LENGTH: usize = "OK".len();
const CANCEL_OFFSET: usize = "OK ".len(); const CANCEL_OFFSET: usize = "OK ".len();
const CANCEL_LENGTH: usize = "Cancel".len();
#[derive(Debug, Copy, PartialEq, Eq, Clone)] #[derive(Debug, Copy, PartialEq, Eq, Clone)]
enum SelectorCursor { enum SelectorCursor {
@ -114,10 +112,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
} }
let shortcuts = self.shortcuts(context); let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() {
highlighted_attrs.attrs |= Attr::REVERSE;
}
match (event, self.cursor) { match (event, self.cursor) {
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => { (UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* User can only select one entry, so Enter key finalises the selection */ /* User can only select one entry, so Enter key finalises the selection */
@ -152,8 +146,8 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
} }
self.done = true; self.done = true;
_ = self.done(); _ = self.done();
context.replies.push_back(self.cancel()); self.cancel(context);
self.set_dirty(true);
return false; return false;
} }
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => { (UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => {
@ -211,26 +205,26 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
self.dirty = true; self.dirty = true;
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Entry(c)) (UIEvent::Input(ref key), SelectorCursor::Entry(_))
if !self.single_only if !self.single_only
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => && shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Ok) (UIEvent::Input(ref key), SelectorCursor::Ok)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{ {
self.cursor = SelectorCursor::Cancel; self.cursor = SelectorCursor::Cancel;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Cancel) (UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), _) (UIEvent::Input(ref key), _)
@ -282,10 +276,6 @@ impl Component for UIConfirmationDialog {
} }
let shortcuts = self.shortcuts(context); let shortcuts = self.shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() {
highlighted_attrs.attrs |= Attr::REVERSE;
}
match (event, self.cursor) { match (event, self.cursor) {
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => { (UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* User can only select one entry, so Enter key finalises the selection */ /* User can only select one entry, so Enter key finalises the selection */
@ -294,13 +284,14 @@ impl Component for UIConfirmationDialog {
context.replies.push_back(event); context.replies.push_back(event);
self.unrealize(context); self.unrealize(context);
} }
self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Entry(c)) if !self.single_only => { (UIEvent::Input(Key::Char('\n')), SelectorCursor::Entry(c)) if !self.single_only => {
/* User can select multiple entries, so Enter key toggles the entry under the /* User can select multiple entries, so Enter key toggles the entry under the
* cursor */ * cursor */
self.entries[c].1 = !self.entries[c].1; self.entries[c].1 = !self.entries[c].1;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => { (UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => {
@ -309,6 +300,7 @@ impl Component for UIConfirmationDialog {
context.replies.push_back(event); context.replies.push_back(event);
self.unrealize(context); self.unrealize(context);
} }
self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(Key::Esc), _) => { (UIEvent::Input(Key::Esc), _) => {
@ -320,8 +312,8 @@ impl Component for UIConfirmationDialog {
} }
self.done = true; self.done = true;
_ = self.done(); _ = self.done();
context.replies.push_back(self.cancel()); self.cancel(context);
self.set_dirty(true);
return false; return false;
} }
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => { (UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => {
@ -333,6 +325,7 @@ impl Component for UIConfirmationDialog {
context.replies.push_back(event); context.replies.push_back(event);
self.unrealize(context); self.unrealize(context);
} }
self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Entry(c)) (UIEvent::Input(ref key), SelectorCursor::Entry(c))
@ -344,7 +337,7 @@ impl Component for UIConfirmationDialog {
self.entries[c - 1].1 = true; self.entries[c - 1].1 = true;
} }
self.cursor = SelectorCursor::Entry(c - 1); self.cursor = SelectorCursor::Entry(c - 1);
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Ok) (UIEvent::Input(ref key), SelectorCursor::Ok)
@ -353,7 +346,7 @@ impl Component for UIConfirmationDialog {
{ {
let c = self.entries.len().saturating_sub(1); let c = self.entries.len().saturating_sub(1);
self.cursor = SelectorCursor::Entry(c); self.cursor = SelectorCursor::Entry(c);
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Unfocused) (UIEvent::Input(ref key), SelectorCursor::Unfocused)
@ -363,7 +356,7 @@ impl Component for UIConfirmationDialog {
self.entries[0].1 = true; self.entries[0].1 = true;
} }
self.cursor = SelectorCursor::Entry(0); self.cursor = SelectorCursor::Entry(0);
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Entry(c)) (UIEvent::Input(ref key), SelectorCursor::Entry(c))
@ -376,29 +369,29 @@ impl Component for UIConfirmationDialog {
self.entries[c + 1].1 = true; self.entries[c + 1].1 = true;
} }
self.cursor = SelectorCursor::Entry(c + 1); self.cursor = SelectorCursor::Entry(c + 1);
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Entry(c)) (UIEvent::Input(ref key), SelectorCursor::Entry(_))
if !self.single_only if !self.single_only
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => && shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Ok) (UIEvent::Input(ref key), SelectorCursor::Ok)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{ {
self.cursor = SelectorCursor::Cancel; self.cursor = SelectorCursor::Cancel;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), SelectorCursor::Cancel) (UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{ {
self.cursor = SelectorCursor::Ok; self.cursor = SelectorCursor::Ok;
self.dirty = true; self.set_dirty(true);
return true; return true;
} }
(UIEvent::Input(ref key), _) (UIEvent::Input(ref key), _)
@ -493,6 +486,11 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
} }
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() {
highlighted_attrs.attrs |= Attr::REVERSE;
}
let shortcuts = context.settings.shortcuts.general.key_values(); let shortcuts = context.settings.shortcuts.general.key_values();
let navigate_help_string = format!( let navigate_help_string = format!(
"Navigate options with {} to go down, {} to go up, select with {}", "Navigate options with {} to go down, {} to go up, select with {}",
@ -504,10 +502,11 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
self.entry_titles.iter().map(|e| e.len()).max().unwrap_or(0) + 3, self.entry_titles.iter().map(|e| e.len()).max().unwrap_or(0) + 3,
std::cmp::max(self.title.len(), navigate_help_string.len()) + 3, std::cmp::max(self.title.len(), navigate_help_string.len()) + 3,
) + 3; ) + 3;
let height = self.entries.len() + { let height = self.entries.len()
/* padding */ // padding
3 + 3
}; // buttons row
+ if self.single_only { 1 } else { 5 };
let dialog_area = area.align_inside( let dialog_area = area.align_inside(
(width, height), (width, height),
self.horizontal_alignment, self.horizontal_alignment,
@ -535,54 +534,57 @@ impl<T: PartialEq + std::fmt::Debug + Clone + Sync + Send, F: 'static + Sync + S
); );
let inner_area = inner_area.skip_cols(1).skip_rows(1); let inner_area = inner_area.skip_cols(1).skip_rows(1);
let width = std::cmp::max( /* Extra room for buttons Okay/Cancel */
OK_CANCEL.len(),
std::cmp::max(
self.entry_titles
.iter()
.max_by_key(|e| e.len())
.map(|v| v.len())
.unwrap_or(0),
self.title.len(),
),
) + 5;
let height = self.entries.len()
+ if self.single_only {
0
} else {
/* Extra room for buttons Okay/Cancel */
2
};
if self.single_only { if self.single_only {
for (i, e) in self.entry_titles.iter().enumerate() { for (i, e) in self.entry_titles.iter().enumerate() {
grid.write_string( let attr = if matches!(self.cursor, SelectorCursor::Entry(e) if e == i) {
e, highlighted_attrs
self.theme_default.fg, } else {
self.theme_default.bg, self.theme_default
self.theme_default.attrs, };
inner_area.nth_row(i), grid.write_string(e, attr.fg, attr.bg, attr.attrs, inner_area.nth_row(i), None);
None,
);
} }
} else { } else {
for (i, e) in self.entry_titles.iter().enumerate() { for (i, e) in self.entry_titles.iter().enumerate() {
let attr = if matches!(self.cursor, SelectorCursor::Entry(e) if e == i) {
highlighted_attrs
} else {
self.theme_default
};
grid.write_string( grid.write_string(
&format!("[{}] {}", if self.entries[i].1 { "x" } else { " " }, e), &format!("[{}] {}", if self.entries[i].1 { "x" } else { " " }, e),
self.theme_default.fg, attr.fg,
self.theme_default.bg, attr.bg,
self.theme_default.attrs, attr.attrs,
inner_area.nth_row(i), inner_area.nth_row(i),
None, None,
); );
} }
let inner_area = inner_area.nth_row(self.entry_titles.len() + 2).skip_cols(2);
let attr = if matches!(self.cursor, SelectorCursor::Ok) {
highlighted_attrs
} else {
self.theme_default
};
let (x, y) = grid.write_string(
OK,
attr.fg,
attr.bg,
attr.attrs | Attr::BOLD,
inner_area,
None,
);
let attr = if matches!(self.cursor, SelectorCursor::Cancel) {
highlighted_attrs
} else {
self.theme_default
};
grid.write_string( grid.write_string(
OK_CANCEL, CANCEL,
self.theme_default.fg, attr.fg,
self.theme_default.bg, attr.bg,
self.theme_default.attrs | Attr::BOLD, attr.attrs,
inner_area inner_area.skip(CANCEL_OFFSET + x, y),
.nth_row(height - 1)
.skip_cols((width - OK_CANCEL.len()) / 2),
None, None,
); );
} }
@ -613,9 +615,11 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> UIDialog<T>
}) })
} }
fn cancel(&mut self) -> UIEvent { fn cancel(&mut self, context: &mut Context) {
let Self { ref id, .. } = self; context.unrealized.insert(self.id());
UIEvent::CanceledUIDialog(*id) context
.replies
.push_back(UIEvent::ComponentUnrealize(self.id()));
} }
} }
@ -640,8 +644,10 @@ impl UIConfirmationDialog {
}) })
} }
fn cancel(&mut self) -> UIEvent { fn cancel(&mut self, context: &mut Context) {
let Self { ref id, .. } = self; context.unrealized.insert(self.id());
UIEvent::CanceledUIDialog(*id) context
.replies
.push_back(UIEvent::ComponentUnrealize(self.id()));
} }
} }

@ -24,7 +24,7 @@ use std::{borrow::Cow, collections::HashMap, time::Duration};
use super::*; use super::*;
use crate::melib::text_processing::TextProcessing; use crate::melib::text_processing::TextProcessing;
#[derive(Debug, PartialEq, Eq, Default)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
enum FormFocus { enum FormFocus {
#[default] #[default]
Fields, Fields,
@ -350,17 +350,27 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
if self.is_dirty() { if self.is_dirty() {
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(area.take_rows(self.layout.len()), theme_default); grid.clear_area(area, theme_default);
let label_attrs = crate::conf::value(context, "widgets.form.label"); let label_attrs = crate::conf::value(context, "widgets.form.label");
let mut highlighted = crate::conf::value(context, "highlight");
if !context.settings.terminal.use_color() {
highlighted.attrs |= Attr::REVERSE;
}
for (i, k) in self.layout.iter().enumerate().rev() { for (i, k) in self.layout.iter().enumerate().rev() {
let theme_attr = if i == self.cursor && self.focus == FormFocus::Fields {
grid.change_theme(area.nth_row(i), highlighted);
highlighted
} else {
label_attrs
};
let v = self.fields.get_mut(k).unwrap(); let v = self.fields.get_mut(k).unwrap();
/* Write field label */ /* Write field label */
grid.write_string( grid.write_string(
k.as_ref(), k.as_ref(),
label_attrs.fg, theme_attr.fg,
label_attrs.bg, theme_attr.bg,
label_attrs.attrs, theme_attr.attrs,
area.nth_row(i).skip_cols(1), area.nth_row(i).skip_cols(1),
None, None,
); );
@ -370,47 +380,28 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
area.nth_row(i).skip_cols(self.field_name_max_length + 3), area.nth_row(i).skip_cols(self.field_name_max_length + 3),
context, context,
); );
grid.change_theme(area.nth_row(i), theme_attr);
/* Highlight if necessary */ /* Highlight if necessary */
if i == self.cursor { if i == self.cursor && self.focus == FormFocus::TextInput {
if self.focus == FormFocus::Fields { v.draw_cursor(
let mut field_attrs = grid,
crate::conf::value(context, "widgets.form.highlighted"); area.nth_row(i).skip_cols(self.field_name_max_length + 3),
if !context.settings.terminal.use_color() { area.nth_row(i + 1)
field_attrs.attrs |= Attr::REVERSE; .skip_cols(self.field_name_max_length + 3),
} context,
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)
.set_bg(field_attrs.bg)
.set_attrs(field_attrs.attrs);
}
}
}
if self.focus == FormFocus::TextInput {
v.draw_cursor(
grid,
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,
);
}
} }
} }
let length = self.layout.len(); let length = self.layout.len();
grid.clear_area(area.skip_rows(length).take_rows(length + 2), theme_default);
if !self.hide_buttons { if !self.hide_buttons {
self.buttons self.buttons
.draw(grid, area.nth_row(length + 3).skip_cols(1), context); .draw(grid, area.skip_rows(length + 3).skip_cols(1), context);
} }
if length + 4 < area.height() { if length + 4 < area.height() {
grid.clear_area(area.skip_rows(length + 3), theme_default); grid.clear_area(area.skip_rows(length + 3 + 1), theme_default);
} }
self.set_dirty(false); self.set_dirty(false);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -623,7 +614,7 @@ where
theme_default.bg theme_default.bg
}, },
Attr::BOLD, Attr::BOLD,
area.skip_cols(len).take_cols(cur_len + len), area.skip_cols(len),
None, None,
); );
len += cur_len + 3; len += cur_len + 3;

Loading…
Cancel
Save