Re-add contact list and editor support

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/312/head
Manos Pitsidianakis 2023-11-14 22:58:59 +02:00
parent ba7a97e90b
commit 54d21f25fd
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
7 changed files with 578 additions and 647 deletions

View File

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

View File

@ -24,15 +24,15 @@ use std::cmp;
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)]
#[derive(Debug)]
enum ViewMode {
List,
View(ComponentId),
View(Box<ContactManager>),
}
#[derive(Debug)]
@ -66,7 +66,6 @@ pub struct ContactList {
menu_visibility: bool,
movement: Option<PageMovement>,
cmd_buf: String,
//view: Option<ContactManager>,
ratio: usize, // right/(container width) * 100
id: ComponentId,
}
@ -104,7 +103,6 @@ impl ContactList {
dirty: true,
movement: None,
cmd_buf: String::with_capacity(8),
//view: None,
ratio: 90,
sidebar_divider: context.settings.listing.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) {
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 {
/* reserve top row for column headers */
let area = area.skip_rows(1);
grid.clear_area(area, self.theme_default);
grid.copy_area(
@ -333,7 +333,7 @@ impl ContactList {
area,
self.data_columns.columns[0].area(),
);
context.dirty_areas.push_back(area);
context.dirty_areas.push_back(total_area);
return;
}
let rows = area.height();
@ -385,7 +385,7 @@ impl ContactList {
ScrollUpdate::Update {
id: self.id,
context: ScrollContext {
shown_lines: top_idx + rows,
shown_lines: (top_idx + rows).min(self.length - top_idx),
total_lines: self.length,
has_more_lines: false,
},
@ -408,7 +408,7 @@ impl ContactList {
if *idx >= self.length {
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);
context.dirty_areas.push_back(new_area);
}
@ -422,17 +422,11 @@ impl ContactList {
}
/* 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);
grid.clear_area(total_area, self.theme_default);
_ = self.data_columns.recalc_widths(area.size(), top_idx);
/* copy table columns */
self.data_columns.draw(
grid,
top_idx,
self.cursor_pos,
grid.bounds_iter(area.skip_rows(1)),
);
self.data_columns
.draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area));
let header_attrs = crate::conf::value(context, "widgets.list.header");
let mut x = 0;
@ -451,18 +445,19 @@ impl ContactList {
header_attrs.fg,
header_attrs.bg,
header_attrs.attrs,
area.skip_cols(x)
header_area
.skip_cols(x)
.take_cols(x + (self.data_columns.widths[i])),
None,
);
x += self.data_columns.widths[i] + 2; // + SEPARATOR
if x > area.width() {
if x > header_area.width() {
break;
}
}
grid.change_theme(area.nth_row(0), header_attrs);
grid.change_theme(header_area, header_attrs);
if top_idx + rows > self.length {
grid.clear_area(
@ -470,21 +465,17 @@ impl ContactList {
self.theme_default,
);
}
self.highlight_line(
grid,
area.nth_row(1 + self.cursor_pos % rows),
self.cursor_pos,
);
context.dirty_areas.push_back(area);
self.highlight_line(grid, area.nth_row(self.cursor_pos % rows), self.cursor_pos);
context.dirty_areas.push_back(total_area);
}
}
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 ViewMode::View(ref mut mgr) = self.mode {
mgr.draw(grid, area, context);
return;
}
if !self.dirty {
return;
@ -531,7 +522,6 @@ impl Component for ContactList {
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");
@ -544,12 +534,6 @@ impl Component for ContactList {
self.initialized = false;
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) => {
self.set_dirty(true);
}
@ -559,314 +543,315 @@ impl Component for ContactList {
_ => {}
}
//if let Some(ref mut v) = self.view {
// if v.process_event(event, context) {
// return true;
// }
//}
if let ViewMode::View(ref mut mgr) = self.mode {
if matches!(event, UIEvent::ComponentUnrealize(id) if *id == mgr.id()) {
mgr.unrealize(context);
self.mode = ViewMode::List;
self.set_dirty(true);
return true;
}
if mgr.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 matches!(self.mode, ViewMode::List) {
match *event {
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::CONTACT_LIST]["create_contact"]) =>
{
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.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;
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;
}
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;
}
// [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::<usize>() {
self.cmd_buf.clear();
self.mode = ViewMode::View(manager);
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),
.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]["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();
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 = 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
.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),
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
ScrollUpdate::End(self.id),
)));
}
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::<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;
};
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::<usize>() {
self.cmd_buf.clear();
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;
}
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);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
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;
};
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;
};
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::<usize>() {
self.cmd_buf.clear();
}
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
mult
} else {
self.cmd_buf.clear();
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
.push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
self.cmd_buf.clone(),
)));
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::<usize>() {
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));
}
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();
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;
};
self.set_dirty(true);
self.movement = Some(PageMovement::PageDown(mult));
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::<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;
};
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::<usize>() {
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::<usize>() {
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;
}
_ => {}
}
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 || matches!(self.mode, ViewMode::View(ref mgr) if mgr.is_dirty())
}
fn set_dirty(&mut self, value: bool) {
//if let Some(p) = self.view.as_mut() {
// p.set_dirty(value);
//};
if let ViewMode::View(ref mut mgr) = self.mode {
mgr.set_dirty(value);
}
self.dirty = value;
}
@ -876,12 +861,13 @@ impl Component for ContactList {
.replies
.push_back(UIEvent::Action(Tab(TabAction::Kill(uuid))));
}
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = ShortcutMaps::default(); //self
//.view
//.as_ref()
//.map(|p| p.shortcuts(context))
//.unwrap_or_default();
let mut map = if let ViewMode::View(ref mgr) = self.mode {
mgr.shortcuts(context)
} else {
ShortcutMaps::default()
};
map.insert(
Shortcuts::CONTACT_LIST,
@ -899,10 +885,11 @@ impl Component for ContactList {
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
//self.view .as_mut() .map(|p| p.can_quit_cleanly(context))
// .unwrap_or(true)
}
fn status(&self, context: &Context) -> String {

View File

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

View File

@ -883,152 +883,139 @@ impl Component for Composer {
self.update_form(context);
self.initialized = true;
}
let header_height = self.form.len();
let theme_default = crate::conf::value(context, "theme_default");
if self.dirty {
grid.clear_area(area, theme_default);
}
let mid = 0;
/*
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_height = self.form.len();
let header_area = area
.skip_rows(1)
.take_rows(header_height)
.skip_cols(mid + 1)
.skip_cols_from_end(mid);
.skip_cols(1)
.skip_cols_from_end(1);
let attachments_no = self.draft.attachments().len();
let attachment_area = area
.skip_rows(header_height)
.skip_rows(header_height + 1)
.skip_rows(
area.height()
.saturating_sub(header_area.height() + 4 + attachments_no),
)
.skip_cols(mid + 1);
.skip_cols(1)
.skip_cols_from_end(1);
let body_area = area
.skip_rows(header_height)
.skip_rows_from_end(attachment_area.height());
.skip_rows(header_height + 2)
.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"));
grid.write_string(
if self.reply_context.is_some() {
"COMPOSING REPLY"
} else {
"COMPOSING MESSAGE"
},
crate::conf::value(context, "highlight").fg,
crate::conf::value(context, "highlight").bg,
crate::conf::value(context, "highlight").attrs,
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(
(
pos_dec(upper_left, (0, 1)),
set_x(bottom_right, get_x(upper_left) + mid),
),
theme_default,
);
grid.clear_area(
(
(
get_x(bottom_right).saturating_sub(mid),
get_y(upper_left).saturating_sub(1),
),
bottom_right,
),
theme_default,
);
*/
if self.dirty {
grid.clear_area(area.nth_row(0), crate::conf::value(context, "highlight"));
grid.write_string(
if self.reply_context.is_some() {
"COMPOSING REPLY"
} else {
"COMPOSING MESSAGE"
},
crate::conf::value(context, "highlight").fg,
crate::conf::value(context, "highlight").bg,
crate::conf::value(context, "highlight").attrs,
area.nth_row(0),
None,
);
}
/* Regardless of view mode, do the following */
if self.dirty {
match self.cursor {
Cursor::Headers => {
grid.change_theme(header_area, theme_default);
}
Cursor::Body => {
grid.change_theme(
body_area,
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.form.draw(grid, header_area, context);
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);
if self.dirty {
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()));
context.dirty_areas.push_back(area);
self.dirty = false;
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(embed_area);
self.dirty = false;
}
return;
}
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);
let our_map: ShortcutMap =
account_settings!(context[self.account_hash].shortcuts.composing)
.key_values();
let mut shortcuts: ShortcutMaps = Default::default();
shortcuts.insert(Shortcuts::COMPOSING, our_map);
let stopped_message: String =
format!("Process with PID {} has stopped.", guard.child_pid);
let stopped_message_2: String = format!(
"-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.";
let max_len = std::cmp::max(
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())),
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)
.key_values();
let mut shortcuts: ShortcutMaps = Default::default();
shortcuts.insert(Shortcuts::COMPOSING, our_map);
let stopped_message: String =
format!("Process with PID {} has stopped.", guard.child_pid);
let stopped_message_2: String = format!(
"-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.";
let max_len = std::cmp::max(
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,
);
}
context.dirty_areas.push_back(area);
self.dirty = false;
}
context.dirty_areas.push_back(area);
self.dirty = false;
return;
}
}
@ -1039,51 +1026,13 @@ impl Component for Composer {
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
// might leave draw artifacts in the remaining area.
grid.clear_area(body_area, theme_default);
self.set_dirty(true);
self.pager.draw(grid, body_area, context);
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 => {}
if self.dirty {
// Force clean pager area, because if body height is less than body_area it will
// might leave draw artifacts in the remaining area.
grid.clear_area(body_area, theme_default);
self.pager.set_dirty(true);
}
self.pager.draw(grid, body_area, context);
//if !self.mode.is_edit_attachments() {
self.draw_attachments(grid, attachment_area, context);
@ -1125,8 +1074,11 @@ impl Component for Composer {
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 {

View File

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

View File

@ -21,11 +21,9 @@
use super::*;
const OK_CANCEL: &str = "OK Cancel";
const OK_OFFSET: usize = 0;
const OK_LENGTH: usize = "OK".len();
const OK: &str = "OK";
const CANCEL: &str = "Cancel";
const CANCEL_OFFSET: usize = "OK ".len();
const CANCEL_LENGTH: usize = "Cancel".len();
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
enum SelectorCursor {
@ -114,10 +112,6 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> Component f
}
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) {
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* 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();
context.replies.push_back(self.cancel());
self.cancel(context);
self.set_dirty(true);
return false;
}
(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;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
(UIEvent::Input(ref key), SelectorCursor::Entry(_))
if !self.single_only
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{
self.cursor = SelectorCursor::Ok;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Ok)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{
self.cursor = SelectorCursor::Cancel;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{
self.cursor = SelectorCursor::Ok;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), _)
@ -282,10 +276,6 @@ impl Component for UIConfirmationDialog {
}
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) {
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* 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);
self.unrealize(context);
}
self.set_dirty(true);
return true;
}
(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
* cursor */
self.entries[c].1 = !self.entries[c].1;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => {
@ -309,6 +300,7 @@ impl Component for UIConfirmationDialog {
context.replies.push_back(event);
self.unrealize(context);
}
self.set_dirty(true);
return true;
}
(UIEvent::Input(Key::Esc), _) => {
@ -320,8 +312,8 @@ impl Component for UIConfirmationDialog {
}
self.done = true;
_ = self.done();
context.replies.push_back(self.cancel());
self.cancel(context);
self.set_dirty(true);
return false;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => {
@ -333,6 +325,7 @@ impl Component for UIConfirmationDialog {
context.replies.push_back(event);
self.unrealize(context);
}
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
@ -344,7 +337,7 @@ impl Component for UIConfirmationDialog {
self.entries[c - 1].1 = true;
}
self.cursor = SelectorCursor::Entry(c - 1);
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Ok)
@ -353,7 +346,7 @@ impl Component for UIConfirmationDialog {
{
let c = self.entries.len().saturating_sub(1);
self.cursor = SelectorCursor::Entry(c);
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Unfocused)
@ -363,7 +356,7 @@ impl Component for UIConfirmationDialog {
self.entries[0].1 = true;
}
self.cursor = SelectorCursor::Entry(0);
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
@ -376,29 +369,29 @@ impl Component for UIConfirmationDialog {
self.entries[c + 1].1 = true;
}
self.cursor = SelectorCursor::Entry(c + 1);
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
(UIEvent::Input(ref key), SelectorCursor::Entry(_))
if !self.single_only
&& shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) =>
{
self.cursor = SelectorCursor::Ok;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Ok)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
{
self.cursor = SelectorCursor::Cancel;
self.dirty = true;
self.set_dirty(true);
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
{
self.cursor = SelectorCursor::Ok;
self.dirty = true;
self.set_dirty(true);
return true;
}
(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) {
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 navigate_help_string = format!(
"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,
std::cmp::max(self.title.len(), navigate_help_string.len()) + 3,
) + 3;
let height = self.entries.len() + {
/* padding */
3
};
let height = self.entries.len()
// padding
+ 3
// buttons row
+ if self.single_only { 1 } else { 5 };
let dialog_area = area.align_inside(
(width, height),
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 width = std::cmp::max(
OK_CANCEL.len(),
std::cmp::max(
self.entry_titles
.iter()
.max_by_key(|e| e.len())
.map(|v| v.len())
.unwrap_or(0),
self.title.len(),
),
) + 5;
let height = self.entries.len()
+ if self.single_only {
0
} else {
/* Extra room for buttons Okay/Cancel */
2
};
/* Extra room for buttons Okay/Cancel */
if self.single_only {
for (i, e) in self.entry_titles.iter().enumerate() {
grid.write_string(
e,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
inner_area.nth_row(i),
None,
);
let attr = if matches!(self.cursor, SelectorCursor::Entry(e) if e == i) {
highlighted_attrs
} else {
self.theme_default
};
grid.write_string(e, attr.fg, attr.bg, attr.attrs, inner_area.nth_row(i), None);
}
} else {
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(
&format!("[{}] {}", if self.entries[i].1 { "x" } else { " " }, e),
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs,
attr.fg,
attr.bg,
attr.attrs,
inner_area.nth_row(i),
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(
OK_CANCEL,
self.theme_default.fg,
self.theme_default.bg,
self.theme_default.attrs | Attr::BOLD,
inner_area
.nth_row(height - 1)
.skip_cols((width - OK_CANCEL.len()) / 2),
CANCEL,
attr.fg,
attr.bg,
attr.attrs,
inner_area.skip(CANCEL_OFFSET + x, y),
None,
);
}
@ -613,9 +615,11 @@ impl<T: 'static + PartialEq + std::fmt::Debug + Clone + Sync + Send> UIDialog<T>
})
}
fn cancel(&mut self) -> UIEvent {
let Self { ref id, .. } = self;
UIEvent::CanceledUIDialog(*id)
fn cancel(&mut self, context: &mut Context) {
context.unrealized.insert(self.id());
context
.replies
.push_back(UIEvent::ComponentUnrealize(self.id()));
}
}
@ -640,8 +644,10 @@ impl UIConfirmationDialog {
})
}
fn cancel(&mut self) -> UIEvent {
let Self { ref id, .. } = self;
UIEvent::CanceledUIDialog(*id)
fn cancel(&mut self, context: &mut Context) {
context.unrealized.insert(self.id());
context
.replies
.push_back(UIEvent::ComponentUnrealize(self.id()));
}
}

View File

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