mirror of
https://git.meli.delivery/meli/meli
synced 2024-11-10 19:10:57 +00:00
contacts: add support for externally managed contacts
Adds support for contacts (Cards) marked as `external_resource` which prevents modifications from happening. No way to import external contacts is added yet.
This commit is contained in:
parent
dc525b9ddd
commit
5beed91df2
@ -81,6 +81,9 @@ pub struct Card {
|
|||||||
color: u8,
|
color: u8,
|
||||||
last_edited: DateTime<Local>,
|
last_edited: DateTime<Local>,
|
||||||
extra_properties: FnvHashMap<String, String>,
|
extra_properties: FnvHashMap<String, String>,
|
||||||
|
|
||||||
|
/// If true, we can't make any changes because we do not manage this resource.
|
||||||
|
external_resource: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressBook {
|
impl AddressBook {
|
||||||
@ -134,6 +137,7 @@ impl Card {
|
|||||||
key: String::new(),
|
key: String::new(),
|
||||||
|
|
||||||
last_edited: Local::now(),
|
last_edited: Local::now(),
|
||||||
|
external_resource: false,
|
||||||
extra_properties: FnvHashMap::default(),
|
extra_properties: FnvHashMap::default(),
|
||||||
color: 0,
|
color: 0,
|
||||||
}
|
}
|
||||||
@ -202,37 +206,50 @@ impl Card {
|
|||||||
pub fn set_extra_property(&mut self, key: &str, value: String) {
|
pub fn set_extra_property(&mut self, key: &str, value: String) {
|
||||||
self.extra_properties.insert(key.to_string(), value);
|
self.extra_properties.insert(key.to_string(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extra_property(&self, key: &str) -> Option<&str> {
|
pub fn extra_property(&self, key: &str) -> Option<&str> {
|
||||||
self.extra_properties.get(key).map(String::as_str)
|
self.extra_properties.get(key).map(String::as_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extra_properties(&self) -> &FnvHashMap<String, String> {
|
||||||
|
&self.extra_properties
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_external_resource(&mut self, new_val: bool) {
|
||||||
|
self.external_resource = new_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn external_resource(&self) -> bool {
|
||||||
|
self.external_resource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FnvHashMap<String, String>> for Card {
|
impl From<FnvHashMap<String, String>> for Card {
|
||||||
fn from(mut map: FnvHashMap<String, String>) -> Card {
|
fn from(mut map: FnvHashMap<String, String>) -> Card {
|
||||||
let mut card = Card::new();
|
let mut card = Card::new();
|
||||||
if let Some(val) = map.remove("Title") {
|
if let Some(val) = map.remove("TITLE") {
|
||||||
card.title = val;
|
card.title = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("Name") {
|
if let Some(val) = map.remove("NAME") {
|
||||||
card.name = val;
|
card.name = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("Additional Name") {
|
if let Some(val) = map.remove("ADDITIONAL NAME") {
|
||||||
card.additionalname = val;
|
card.additionalname = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("Name Prefix") {
|
if let Some(val) = map.remove("NAME PREFIX") {
|
||||||
card.name_prefix = val;
|
card.name_prefix = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("Name Suffix") {
|
if let Some(val) = map.remove("NAME SUFFIX") {
|
||||||
card.name_suffix = val;
|
card.name_suffix = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(val) = map.remove("E-mail") {
|
if let Some(val) = map.remove("E-MAIL") {
|
||||||
card.email = val;
|
card.email = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("url") {
|
if let Some(val) = map.remove("URL") {
|
||||||
card.url = val;
|
card.url = val;
|
||||||
}
|
}
|
||||||
if let Some(val) = map.remove("key") {
|
if let Some(val) = map.remove("KEY") {
|
||||||
card.key = val;
|
card.key = val;
|
||||||
}
|
}
|
||||||
card.extra_properties = map;
|
card.extra_properties = map;
|
||||||
|
@ -65,7 +65,7 @@ pub struct ContentLine {
|
|||||||
impl CardDeserializer {
|
impl CardDeserializer {
|
||||||
pub fn from_str(mut input: &str) -> Result<VCard<impl VCardVersion>> {
|
pub fn from_str(mut input: &str) -> Result<VCard<impl VCardVersion>> {
|
||||||
input = if !input.starts_with(HEADER) || !input.ends_with(FOOTER) {
|
input = if !input.starts_with(HEADER) || !input.ends_with(FOOTER) {
|
||||||
return Err(MeliError::new(format!("Error while parsing vcard: input does not start or end with correct header and footer. input is:\n{}", input)));
|
return Err(MeliError::new(format!("Error while parsing vcard: input does not start or end with correct header and footer. input is:\n{:?}", input)));
|
||||||
} else {
|
} else {
|
||||||
&input[HEADER.len()..input.len() - FOOTER.len()]
|
&input[HEADER.len()..input.len() - FOOTER.len()]
|
||||||
};
|
};
|
||||||
|
@ -28,21 +28,24 @@ pub use self::contact_list::*;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
//ReadOnly,
|
ReadOnly,
|
||||||
Read,
|
Discard(Selector<char>),
|
||||||
//Edit,
|
Edit,
|
||||||
//New,
|
//New,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContactManager {
|
pub struct ContactManager {
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
|
parent_id: ComponentId,
|
||||||
pub card: Card,
|
pub card: Card,
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
form: FormWidget,
|
form: FormWidget,
|
||||||
account_pos: usize,
|
account_pos: usize,
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
|
has_changes: bool,
|
||||||
|
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,12 +53,14 @@ impl Default for ContactManager {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ContactManager {
|
ContactManager {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
|
parent_id: Uuid::nil(),
|
||||||
card: Card::new(),
|
card: Card::new(),
|
||||||
mode: ViewMode::Read,
|
mode: ViewMode::Edit,
|
||||||
form: FormWidget::default(),
|
form: FormWidget::default(),
|
||||||
account_pos: 0,
|
account_pos: 0,
|
||||||
content: CellBuffer::new(200, 100, Cell::with_char(' ')),
|
content: CellBuffer::new(100, 1, Cell::with_char(' ')),
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
has_changes: false,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,20 +82,36 @@ impl ContactManager {
|
|||||||
Color::Byte(250),
|
Color::Byte(250),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::Default,
|
Attr::Default,
|
||||||
((0, 0), (width, 0)),
|
((0, 0), (width - 1, 0)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&self.card.last_edited(),
|
&self.card.last_edited(),
|
||||||
&mut self.content,
|
&mut self.content,
|
||||||
Color::Byte(250),
|
Color::Byte(250),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::Default,
|
Attr::Default,
|
||||||
((x, 0), (width, 0)),
|
((x, 0), (width - 1, 0)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.card.external_resource() {
|
||||||
|
self.mode = ViewMode::ReadOnly;
|
||||||
|
self.content
|
||||||
|
.resize(self.content.size().0, 2, Cell::default());
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
"This contact's origin is external and cannot be edited within meli.",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Byte(250),
|
||||||
|
Color::Default,
|
||||||
|
Attr::Default,
|
||||||
|
((x, y), (width - 1, y)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.form = FormWidget::new("Save".into());
|
self.form = FormWidget::new("Save".into());
|
||||||
self.form.add_button(("Cancel".into(), false));
|
self.form.add_button(("Cancel(Esc)".into(), false));
|
||||||
self.form
|
self.form
|
||||||
.push(("NAME".into(), self.card.name().to_string()));
|
.push(("NAME".into(), self.card.name().to_string()));
|
||||||
self.form.push((
|
self.form.push((
|
||||||
@ -105,6 +126,13 @@ impl ContactManager {
|
|||||||
.push(("E-MAIL".into(), self.card.email().to_string()));
|
.push(("E-MAIL".into(), self.card.email().to_string()));
|
||||||
self.form.push(("URL".into(), self.card.url().to_string()));
|
self.form.push(("URL".into(), self.card.url().to_string()));
|
||||||
self.form.push(("KEY".into(), self.card.key().to_string()));
|
self.form.push(("KEY".into(), self.card.key().to_string()));
|
||||||
|
for (k, v) in self.card.extra_properties() {
|
||||||
|
self.form.push((k.into(), v.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_parent_id(&mut self, new_val: ComponentId) {
|
||||||
|
self.parent_id = new_val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,87 +142,166 @@ impl Component for ContactManager {
|
|||||||
self.initialize();
|
self.initialize();
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
}
|
}
|
||||||
clear_area(grid, area);
|
|
||||||
let (width, _height) = self.content.size();
|
|
||||||
copy_area(grid, &self.content, area, ((0, 0), (width - 1, 0)));
|
|
||||||
|
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
|
if self.dirty {
|
||||||
|
let (width, _height) = self.content.size();
|
||||||
|
clear_area(
|
||||||
|
grid,
|
||||||
|
(upper_left, set_y(bottom_right, get_y(upper_left) + 1)),
|
||||||
|
);
|
||||||
|
copy_area_with_break(grid, &self.content, area, ((0, 0), (width - 1, 0)));
|
||||||
|
self.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
self.form.draw(
|
self.form.draw(
|
||||||
grid,
|
grid,
|
||||||
(set_y(upper_left, get_y(upper_left) + 1), bottom_right),
|
(set_y(upper_left, get_y(upper_left) + 2), bottom_right),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
match self.mode {
|
||||||
|
ViewMode::Discard(ref mut selector) => {
|
||||||
|
/* Let user choose whether to quit with/without saving or cancel */
|
||||||
|
selector.draw(grid, center_area(area, selector.content.size()), context);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 self.form.process_event(event, context) {
|
match self.mode {
|
||||||
match self.form.buttons_result() {
|
ViewMode::Discard(ref mut selector) => {
|
||||||
None => {}
|
if selector.process_event(event, context) {
|
||||||
Some(true) => {
|
if selector.is_done() {
|
||||||
let fields = std::mem::replace(&mut self.form, FormWidget::default())
|
let s = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
|
||||||
.collect()
|
ViewMode::Discard(s) => s,
|
||||||
.unwrap();
|
_ => unreachable!(),
|
||||||
let fields: FnvHashMap<String, String> = fields
|
};
|
||||||
.into_iter()
|
let key = s.collect()[0] as char;
|
||||||
.map(|(s, v)| {
|
match key {
|
||||||
(
|
'x' => {
|
||||||
s,
|
context
|
||||||
match v {
|
.replies
|
||||||
Field::Text(v, _) => v.as_str().to_string(),
|
.push_back(UIEvent::Action(Tab(Kill(self.parent_id))));
|
||||||
Field::Choice(mut v, c) => v.remove(c),
|
return true;
|
||||||
},
|
}
|
||||||
)
|
'n' => {}
|
||||||
})
|
'y' => {}
|
||||||
.collect();
|
_ => {}
|
||||||
let mut new_card = Card::from(fields);
|
}
|
||||||
new_card.set_id(*self.card.id());
|
}
|
||||||
context.accounts[self.account_pos]
|
self.set_dirty();
|
||||||
.address_book
|
return true;
|
||||||
.add_card(new_card);
|
}
|
||||||
context
|
}
|
||||||
.replies
|
ViewMode::Edit => {
|
||||||
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
|
if let &mut UIEvent::Input(Key::Esc) = event {
|
||||||
"Saved.".into(),
|
if self.can_quit_cleanly(context) {
|
||||||
)));
|
context
|
||||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
.replies
|
||||||
}
|
.push_back(UIEvent::Action(Tab(Kill(self.parent_id))));
|
||||||
Some(false) => {
|
}
|
||||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
return true;
|
||||||
|
}
|
||||||
|
if self.form.process_event(event, context) {
|
||||||
|
match self.form.buttons_result() {
|
||||||
|
None => {}
|
||||||
|
Some(true) => {
|
||||||
|
let fields = std::mem::replace(&mut self.form, FormWidget::default())
|
||||||
|
.collect()
|
||||||
|
.unwrap();
|
||||||
|
let fields: FnvHashMap<String, String> = fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|(s, v)| {
|
||||||
|
(
|
||||||
|
s,
|
||||||
|
match v {
|
||||||
|
Field::Text(v, _) => v.as_str().to_string(),
|
||||||
|
Field::Choice(mut v, c) => v.remove(c),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut new_card = Card::from(fields);
|
||||||
|
new_card.set_id(*self.card.id());
|
||||||
|
context.accounts[self.account_pos]
|
||||||
|
.address_book
|
||||||
|
.add_card(new_card);
|
||||||
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::DisplayMessage("Saved.".into()),
|
||||||
|
));
|
||||||
|
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||||
|
}
|
||||||
|
Some(false) => {
|
||||||
|
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.set_dirty();
|
||||||
|
if let UIEvent::InsertInput(_) = event {
|
||||||
|
self.has_changes = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ViewMode::ReadOnly => {
|
||||||
|
if let &mut UIEvent::Input(Key::Esc) = event {
|
||||||
|
if self.can_quit_cleanly(context) {
|
||||||
|
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
match *event {
|
|
||||||
UIEvent::Input(Key::Char('\n')) => {
|
|
||||||
context.replies.push_back(UIEvent {
|
|
||||||
id: 0,
|
|
||||||
event_type: UIEvent::ComponentKill(self.id),
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty | self.form.is_dirty()
|
self.dirty
|
||||||
|
|| self.form.is_dirty()
|
||||||
|
|| if let ViewMode::Discard(ref selector) = self.mode {
|
||||||
|
selector.is_dirty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_dirty(&mut self) {
|
fn set_dirty(&mut self) {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.initialized = false;
|
|
||||||
self.form.set_dirty();
|
self.form.set_dirty();
|
||||||
|
if let ViewMode::Discard(ref mut selector) = self.mode {
|
||||||
|
selector.set_dirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> ComponentId {
|
fn id(&self) -> ComponentId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_id(&mut self, id: ComponentId) {
|
fn set_id(&mut self, id: ComponentId) {
|
||||||
self.id = id;
|
self.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||||
|
if !self.has_changes {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Play it safe and ask user for confirmation */
|
||||||
|
self.mode = ViewMode::Discard(Selector::new(
|
||||||
|
"this contact has unsaved changes",
|
||||||
|
vec![
|
||||||
|
('x', "quit without saving".to_string()),
|
||||||
|
('y', "save draft and quit".to_string()),
|
||||||
|
('n', "cancel".to_string()),
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
context,
|
||||||
|
));
|
||||||
|
self.set_dirty();
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use melib::CardId;
|
use melib::CardId;
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
const MAX_COLS: usize = 500;
|
const MAX_COLS: usize = 500;
|
||||||
|
|
||||||
@ -16,13 +17,15 @@ pub struct ContactList {
|
|||||||
new_cursor_pos: usize,
|
new_cursor_pos: usize,
|
||||||
account_pos: usize,
|
account_pos: usize,
|
||||||
length: usize,
|
length: usize,
|
||||||
content: CellBuffer,
|
data_columns: DataColumns,
|
||||||
|
initialized: bool,
|
||||||
|
|
||||||
id_positions: Vec<CardId>,
|
id_positions: Vec<CardId>,
|
||||||
|
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
view: Option<Box<dyn Component>>,
|
movement: Option<PageMovement>,
|
||||||
|
view: Option<ContactManager>,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +44,6 @@ impl fmt::Display for ContactList {
|
|||||||
impl ContactList {
|
impl ContactList {
|
||||||
const DESCRIPTION: &'static str = "contact list";
|
const DESCRIPTION: &'static str = "contact list";
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
|
||||||
ContactList {
|
ContactList {
|
||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
new_cursor_pos: 0,
|
new_cursor_pos: 0,
|
||||||
@ -49,8 +51,10 @@ impl ContactList {
|
|||||||
account_pos: 0,
|
account_pos: 0,
|
||||||
id_positions: Vec::new(),
|
id_positions: Vec::new(),
|
||||||
mode: ViewMode::List,
|
mode: ViewMode::List,
|
||||||
content,
|
data_columns: DataColumns::default(),
|
||||||
|
initialized: false,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
movement: None,
|
||||||
view: None,
|
view: None,
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
}
|
}
|
||||||
@ -67,91 +71,141 @@ impl ContactList {
|
|||||||
let account = &mut context.accounts[self.account_pos];
|
let account = &mut context.accounts[self.account_pos];
|
||||||
let book = &mut account.address_book;
|
let book = &mut account.address_book;
|
||||||
self.length = book.len();
|
self.length = book.len();
|
||||||
self.content
|
|
||||||
.resize(MAX_COLS, book.len() + 1, Cell::with_char(' '));
|
|
||||||
|
|
||||||
clear_area(&mut self.content, ((0, 0), (MAX_COLS - 1, self.length)));
|
|
||||||
|
|
||||||
self.id_positions.clear();
|
self.id_positions.clear();
|
||||||
if self.id_positions.capacity() < book.len() {
|
if self.id_positions.capacity() < book.len() {
|
||||||
self.id_positions.reserve(book.len());
|
self.id_positions.reserve(book.len());
|
||||||
}
|
}
|
||||||
let mut maxima = ("Name".len(), "E-mail".len());
|
self.dirty = true;
|
||||||
|
let mut min_width = ("Name".len(), "E-mail".len(), 0, 0, 0);
|
||||||
|
|
||||||
for c in book.values() {
|
for c in book.values() {
|
||||||
self.id_positions.push(*c.id());
|
self.id_positions.push(*c.id());
|
||||||
maxima.0 = std::cmp::max(maxima.0, c.name().split_graphemes().len());
|
min_width.0 = cmp::max(min_width.0, c.name().split_graphemes().len()); /* name */
|
||||||
maxima.1 = std::cmp::max(maxima.1, c.email().split_graphemes().len());
|
min_width.1 = cmp::max(min_width.1, c.email().split_graphemes().len()); /* email */
|
||||||
|
min_width.2 = cmp::max(min_width.2, c.url().split_graphemes().len());
|
||||||
|
/* url */
|
||||||
}
|
}
|
||||||
maxima.0 += 5;
|
|
||||||
maxima.1 += maxima.0 + 5;
|
/* name column */
|
||||||
|
self.data_columns.columns[0] = CellBuffer::new_with_context(
|
||||||
|
min_width.0,
|
||||||
|
self.length + 1,
|
||||||
|
Cell::with_char(' '),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
/* email column */
|
||||||
|
self.data_columns.columns[1] = CellBuffer::new_with_context(
|
||||||
|
min_width.1,
|
||||||
|
self.length + 1,
|
||||||
|
Cell::with_char(' '),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
/* url column */
|
||||||
|
self.data_columns.columns[2] = CellBuffer::new_with_context(
|
||||||
|
min_width.2,
|
||||||
|
self.length + 1,
|
||||||
|
Cell::with_char(' '),
|
||||||
|
context,
|
||||||
|
);
|
||||||
let (x, _) = write_string_to_grid(
|
let (x, _) = write_string_to_grid(
|
||||||
"NAME",
|
"NAME",
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[0],
|
||||||
Color::Black,
|
Color::Black,
|
||||||
Color::White,
|
Color::White,
|
||||||
Attr::Default,
|
Attr::Bold,
|
||||||
((0, 0), (MAX_COLS - 1, self.length)),
|
((0, 0), (MAX_COLS - 1, self.length)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
for x in x..maxima.0 {
|
|
||||||
self.content[(x, 0)].set_bg(Color::White);
|
|
||||||
}
|
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
"E-MAIL",
|
"E-MAIL",
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[1],
|
||||||
Color::Black,
|
Color::Black,
|
||||||
Color::White,
|
Color::White,
|
||||||
Attr::Default,
|
Attr::Bold,
|
||||||
((maxima.0, 0), (MAX_COLS - 1, self.length)),
|
((0, 0), (MAX_COLS - 1, self.length)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
for x in x..maxima.1 {
|
|
||||||
self.content[(x, 0)].set_bg(Color::White);
|
|
||||||
}
|
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
"URL",
|
"URL",
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[2],
|
||||||
Color::Black,
|
Color::Black,
|
||||||
Color::White,
|
Color::White,
|
||||||
Attr::Default,
|
Attr::Bold,
|
||||||
((maxima.1, 0), (MAX_COLS - 1, self.length)),
|
((0, 0), (MAX_COLS - 1, self.length)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
for x in x..(MAX_COLS - 1) {
|
|
||||||
self.content[(x, 0)].set_bg(Color::White);
|
let account = &mut context.accounts[self.account_pos];
|
||||||
}
|
let book = &mut account.address_book;
|
||||||
for (i, c) in book.values().enumerate() {
|
for (idx, c) in book.values().enumerate() {
|
||||||
self.id_positions.push(*c.id());
|
self.id_positions.push(*c.id());
|
||||||
|
|
||||||
write_string_to_grid(
|
let (x, _) = write_string_to_grid(
|
||||||
c.name(),
|
c.name(),
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[0],
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::Default,
|
Attr::Default,
|
||||||
((0, i + 1), (MAX_COLS - 1, self.length)),
|
((0, idx + 1), (min_width.0, idx + 1)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
write_string_to_grid(
|
|
||||||
|
let (x, _) = write_string_to_grid(
|
||||||
c.email(),
|
c.email(),
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[1],
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::Default,
|
Attr::Default,
|
||||||
((maxima.0, i + 1), (MAX_COLS - 1, self.length)),
|
((0, idx + 1), (min_width.1, idx + 1)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
write_string_to_grid(
|
|
||||||
|
let (x, _) = write_string_to_grid(
|
||||||
c.url(),
|
c.url(),
|
||||||
&mut self.content,
|
&mut self.data_columns.columns[2],
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::Default,
|
Attr::Default,
|
||||||
((maxima.1, i + 1), (MAX_COLS - 1, self.length)),
|
((0, idx + 1), (min_width.2, idx + 1)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.length == 0 {
|
||||||
|
let message = "Address book is empty.".to_string();
|
||||||
|
self.data_columns.columns[0] = CellBuffer::new_with_context(
|
||||||
|
message.len(),
|
||||||
|
self.length + 1,
|
||||||
|
Cell::with_char(' '),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
&message,
|
||||||
|
&mut self.data_columns.columns[0],
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
Attr::Default,
|
||||||
|
((0, 0), (MAX_COLS - 1, 0)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
|
/* Reset previously highlighted line */
|
||||||
|
let fg_color = Color::Default;
|
||||||
|
let bg_color = if idx == self.new_cursor_pos {
|
||||||
|
Color::Byte(246)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
change_colors(grid, area, fg_color, bg_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,49 +216,163 @@ impl Component for ContactList {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.dirty {
|
if !self.dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
if !self.initialized {
|
||||||
self.initialize(context);
|
self.initialize(context);
|
||||||
copy_area(
|
|
||||||
grid,
|
|
||||||
&self.content,
|
|
||||||
area,
|
|
||||||
(
|
|
||||||
(0, 0),
|
|
||||||
(MAX_COLS - 1, self.content.size().1.saturating_sub(1)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
context.dirty_areas.push_back(area);
|
|
||||||
self.dirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
/* Reset previously highlighted line */
|
if self.length == 0 {
|
||||||
let fg_color = Color::Default;
|
clear_area(grid, area);
|
||||||
let bg_color = Color::Default;
|
copy_area(
|
||||||
|
grid,
|
||||||
|
&self.data_columns.columns[0],
|
||||||
|
area,
|
||||||
|
((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))),
|
||||||
|
);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
|
||||||
|
if let Some(mvm) = self.movement.take() {
|
||||||
|
match mvm {
|
||||||
|
PageMovement::PageUp => {
|
||||||
|
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(rows);
|
||||||
|
}
|
||||||
|
PageMovement::PageDown => {
|
||||||
|
if self.new_cursor_pos + rows + 1 < self.length {
|
||||||
|
self.new_cursor_pos += rows;
|
||||||
|
} else if self.new_cursor_pos + rows > self.length {
|
||||||
|
self.new_cursor_pos = self.length - 1;
|
||||||
|
} else {
|
||||||
|
self.new_cursor_pos = (self.length / rows) * rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PageMovement::Home => {
|
||||||
|
self.new_cursor_pos = 0;
|
||||||
|
}
|
||||||
|
PageMovement::End => {
|
||||||
|
if self.new_cursor_pos + rows > self.length {
|
||||||
|
self.new_cursor_pos = self.length - 1;
|
||||||
|
} else {
|
||||||
|
self.new_cursor_pos = (self.length / rows) * rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_page_no = (self.cursor_pos).wrapping_div(rows);
|
||||||
|
let page_no = (self.new_cursor_pos).wrapping_div(rows);
|
||||||
|
|
||||||
|
let top_idx = page_no * rows;
|
||||||
|
|
||||||
|
/* If cursor position has changed, remove the highlight from the previous position and
|
||||||
|
* apply it in the new one. */
|
||||||
|
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
|
||||||
|
let old_cursor_pos = self.cursor_pos;
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
for idx in &[old_cursor_pos, self.new_cursor_pos] {
|
||||||
|
if *idx >= self.length {
|
||||||
|
continue; //bounds check
|
||||||
|
}
|
||||||
|
let new_area = (
|
||||||
|
set_y(upper_left, get_y(upper_left) + (*idx % rows) + 1),
|
||||||
|
set_y(bottom_right, get_y(upper_left) + (*idx % rows) + 1),
|
||||||
|
);
|
||||||
|
self.highlight_line(grid, new_area, *idx);
|
||||||
|
context.dirty_areas.push_back(new_area);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if self.cursor_pos != self.new_cursor_pos {
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
}
|
||||||
|
if self.new_cursor_pos >= self.length {
|
||||||
|
self.new_cursor_pos = self.length - 1;
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = width!(area);
|
||||||
|
self.data_columns.widths = Default::default();
|
||||||
|
self.data_columns.widths[0] = self.data_columns.columns[0].size().0; /* name */
|
||||||
|
self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email*/
|
||||||
|
self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* url */
|
||||||
|
|
||||||
|
let min_col_width = std::cmp::min(
|
||||||
|
15,
|
||||||
|
std::cmp::min(self.data_columns.widths[0], self.data_columns.widths[1]),
|
||||||
|
);
|
||||||
|
if self.data_columns.widths[0] + self.data_columns.widths[1] + 3 * min_col_width + 8 > width
|
||||||
|
{
|
||||||
|
let remainder =
|
||||||
|
width.saturating_sub(self.data_columns.widths[0] + self.data_columns.widths[1] + 4);
|
||||||
|
self.data_columns.widths[2] = remainder / 6;
|
||||||
|
}
|
||||||
|
clear_area(grid, area);
|
||||||
|
/* Page_no has changed, so draw new page */
|
||||||
|
let mut x = get_x(upper_left);
|
||||||
|
for i in 0..self.data_columns.columns.len() {
|
||||||
|
let (column_width, column_height) = self.data_columns.columns[i].size();
|
||||||
|
if self.data_columns.widths[i] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
copy_area(
|
||||||
|
grid,
|
||||||
|
&self.data_columns.columns[i],
|
||||||
|
(
|
||||||
|
set_x(upper_left, x),
|
||||||
|
set_x(
|
||||||
|
bottom_right,
|
||||||
|
std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(0, top_idx),
|
||||||
|
(
|
||||||
|
column_width.saturating_sub(1),
|
||||||
|
column_height.saturating_sub(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
x += self.data_columns.widths[i] + 2; // + SEPARATOR
|
||||||
|
if x > get_x(bottom_right) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
change_colors(
|
change_colors(
|
||||||
grid,
|
grid,
|
||||||
(
|
(upper_left, set_y(bottom_right, get_y(upper_left))),
|
||||||
pos_inc(upper_left, (0, self.cursor_pos + 1)),
|
Color::Black,
|
||||||
set_y(bottom_right, get_y(upper_left) + self.cursor_pos + 1),
|
Color::White,
|
||||||
),
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Highlight current line */
|
if top_idx + rows + 1 > self.length {
|
||||||
let bg_color = Color::Byte(246);
|
clear_area(
|
||||||
change_colors(
|
grid,
|
||||||
|
(
|
||||||
|
pos_inc(upper_left, (0, self.length - top_idx + 2)),
|
||||||
|
bottom_right,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.highlight_line(
|
||||||
grid,
|
grid,
|
||||||
(
|
(
|
||||||
pos_inc(upper_left, (0, self.new_cursor_pos + 1)),
|
set_y(upper_left, get_y(upper_left) + (self.cursor_pos % rows) + 1),
|
||||||
set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos + 1),
|
set_y(
|
||||||
|
bottom_right,
|
||||||
|
get_y(upper_left) + (self.cursor_pos % rows) + 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
fg_color,
|
self.cursor_pos,
|
||||||
bg_color,
|
|
||||||
);
|
);
|
||||||
self.cursor_pos = self.new_cursor_pos;
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
@ -215,39 +383,44 @@ impl Component for ContactList {
|
|||||||
}
|
}
|
||||||
let shortcuts = &self.get_shortcuts(context)[Self::DESCRIPTION];
|
let shortcuts = &self.get_shortcuts(context)[Self::DESCRIPTION];
|
||||||
match *event {
|
match *event {
|
||||||
UIEvent::Input(ref key) if *key == shortcuts["create_contact"] => {
|
UIEvent::Input(ref key)
|
||||||
|
if *key == shortcuts["create_contact"] && self.view.is_none() =>
|
||||||
|
{
|
||||||
let mut manager = ContactManager::default();
|
let mut manager = ContactManager::default();
|
||||||
|
manager.set_parent_id(self.id);
|
||||||
manager.account_pos = self.account_pos;
|
manager.account_pos = self.account_pos;
|
||||||
let component = Box::new(manager);
|
|
||||||
|
|
||||||
self.mode = ViewMode::View(component.id());
|
self.mode = ViewMode::View(manager.id());
|
||||||
self.view = Some(component);
|
self.view = Some(manager);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIEvent::Input(ref key) if *key == shortcuts["edit_contact"] && self.length > 0 => {
|
UIEvent::Input(ref key)
|
||||||
|
if *key == shortcuts["edit_contact"] && self.length > 0 && self.view.is_none() =>
|
||||||
|
{
|
||||||
let account = &mut context.accounts[self.account_pos];
|
let account = &mut context.accounts[self.account_pos];
|
||||||
let book = &mut account.address_book;
|
let book = &mut account.address_book;
|
||||||
let card = book[&self.id_positions[self.cursor_pos]].clone();
|
let card = book[&self.id_positions[self.cursor_pos]].clone();
|
||||||
let mut manager = ContactManager::default();
|
let mut manager = ContactManager::default();
|
||||||
|
manager.set_parent_id(self.id);
|
||||||
manager.card = card;
|
manager.card = card;
|
||||||
manager.account_pos = self.account_pos;
|
manager.account_pos = self.account_pos;
|
||||||
let component = Box::new(manager);
|
|
||||||
|
|
||||||
self.mode = ViewMode::View(component.id());
|
self.mode = ViewMode::View(manager.id());
|
||||||
self.view = Some(component);
|
self.view = Some(manager);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Input(Key::Char('n')) => {
|
UIEvent::Input(Key::Char('n')) if self.view.is_none() => {
|
||||||
let card = Card::new();
|
let card = Card::new();
|
||||||
let mut manager = ContactManager::default();
|
let mut manager = ContactManager::default();
|
||||||
|
manager.set_parent_id(self.id);
|
||||||
manager.card = card;
|
manager.card = card;
|
||||||
manager.account_pos = self.account_pos;
|
manager.account_pos = self.account_pos;
|
||||||
let component = Box::new(manager);
|
|
||||||
self.mode = ViewMode::View(component.id());
|
self.mode = ViewMode::View(manager.id());
|
||||||
self.view = Some(component);
|
self.view = Some(manager);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -267,6 +440,12 @@ impl Component for ContactList {
|
|||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||||
|
self.set_dirty();
|
||||||
|
}
|
||||||
|
UIEvent::Resize => {
|
||||||
|
self.set_dirty();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@ -297,13 +476,10 @@ impl Component for ContactList {
|
|||||||
let config_map = context.settings.shortcuts.contact_list.key_values();
|
let config_map = context.settings.shortcuts.contact_list.key_values();
|
||||||
map.insert(
|
map.insert(
|
||||||
self.to_string(),
|
self.to_string(),
|
||||||
[
|
config_map
|
||||||
("create_contact", (*config_map["create_contact"]).clone()),
|
.into_iter()
|
||||||
("edit_contact", (*config_map["edit_contact"]).clone()),
|
.map(|(k, v)| (k, v.clone()))
|
||||||
]
|
.collect(),
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
map
|
map
|
||||||
@ -312,7 +488,15 @@ impl Component for ContactList {
|
|||||||
fn id(&self) -> ComponentId {
|
fn id(&self) -> ComponentId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_id(&mut self, id: ComponentId) {
|
fn set_id(&mut self, id: ComponentId) {
|
||||||
self.id = id;
|
self.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||||
|
self.view
|
||||||
|
.as_mut()
|
||||||
|
.map(|p| p.can_quit_cleanly(context))
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ mod plain;
|
|||||||
pub use self::plain::*;
|
pub use self::plain::*;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub(in crate::listing) struct DataColumns {
|
pub struct DataColumns {
|
||||||
columns: [CellBuffer; 12],
|
pub columns: [CellBuffer; 12],
|
||||||
widths: [usize; 12], // widths of columns calculated in first draw and after size changes
|
pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -52,7 +52,7 @@ pub(in crate::listing) struct CachedSearchStrings {
|
|||||||
body: String,
|
body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ListingTrait {
|
pub trait ListingTrait {
|
||||||
fn coordinates(&self) -> (usize, usize, Option<EnvelopeHash>);
|
fn coordinates(&self) -> (usize, usize, Option<EnvelopeHash>);
|
||||||
fn set_coordinates(&mut self, _: (usize, usize, Option<EnvelopeHash>));
|
fn set_coordinates(&mut self, _: (usize, usize, Option<EnvelopeHash>));
|
||||||
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);
|
||||||
|
@ -238,6 +238,7 @@ impl FormWidget {
|
|||||||
focus: FormFocus::Fields,
|
focus: FormFocus::Fields,
|
||||||
hide_buttons: false,
|
hide_buttons: false,
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
|
dirty: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,75 +311,100 @@ impl Component for FormWidget {
|
|||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
for (i, k) in self.layout.iter().enumerate() {
|
if self.dirty {
|
||||||
let v = self.fields.get_mut(k).unwrap();
|
clear_area(
|
||||||
/* Write field label */
|
|
||||||
write_string_to_grid(
|
|
||||||
k.as_str(),
|
|
||||||
grid,
|
|
||||||
Color::Default,
|
|
||||||
Color::Default,
|
|
||||||
Attr::Default,
|
|
||||||
(
|
|
||||||
pos_inc(upper_left, (1, i)),
|
|
||||||
set_y(bottom_right, i + get_y(upper_left)),
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
/* draw field */
|
|
||||||
v.draw(
|
|
||||||
grid,
|
grid,
|
||||||
(
|
(
|
||||||
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
|
upper_left,
|
||||||
set_y(bottom_right, i + get_y(upper_left)),
|
set_y(bottom_right, get_y(upper_left) + self.layout.len()),
|
||||||
),
|
),
|
||||||
context,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Highlight if necessary */
|
for (i, k) in self.layout.iter().enumerate() {
|
||||||
if i == self.cursor {
|
let v = self.fields.get_mut(k).unwrap();
|
||||||
if self.focus == FormFocus::Fields {
|
/* Write field label */
|
||||||
change_colors(
|
write_string_to_grid(
|
||||||
grid,
|
k.as_str(),
|
||||||
(
|
grid,
|
||||||
pos_inc(upper_left, (0, i)),
|
Color::Default,
|
||||||
set_y(bottom_right, i + get_y(upper_left)),
|
Color::Default,
|
||||||
),
|
Attr::Bold,
|
||||||
Color::Default,
|
(
|
||||||
Color::Byte(246),
|
pos_inc(upper_left, (1, i)),
|
||||||
);
|
set_y(bottom_right, i + get_y(upper_left)),
|
||||||
}
|
),
|
||||||
if self.focus == FormFocus::TextInput {
|
false,
|
||||||
v.draw_cursor(
|
);
|
||||||
grid,
|
/* draw field */
|
||||||
(
|
v.draw(
|
||||||
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
|
grid,
|
||||||
|
(
|
||||||
|
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
|
||||||
|
set_y(bottom_right, i + get_y(upper_left)),
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Highlight if necessary */
|
||||||
|
if i == self.cursor {
|
||||||
|
if self.focus == FormFocus::Fields {
|
||||||
|
change_colors(
|
||||||
|
grid,
|
||||||
(
|
(
|
||||||
get_x(upper_left) + self.field_name_max_length + 3,
|
pos_inc(upper_left, (0, i)),
|
||||||
i + get_y(upper_left),
|
set_y(bottom_right, i + get_y(upper_left)),
|
||||||
),
|
),
|
||||||
),
|
Color::Default,
|
||||||
(
|
Color::Byte(246),
|
||||||
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
|
);
|
||||||
bottom_right,
|
}
|
||||||
),
|
if self.focus == FormFocus::TextInput {
|
||||||
context,
|
v.draw_cursor(
|
||||||
);
|
grid,
|
||||||
|
(
|
||||||
|
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
|
||||||
|
(
|
||||||
|
get_x(upper_left) + self.field_name_max_length + 3,
|
||||||
|
i + get_y(upper_left),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
|
||||||
|
bottom_right,
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !self.hide_buttons {
|
|
||||||
let length = self.layout.len();
|
let length = self.layout.len();
|
||||||
self.buttons.draw(
|
clear_area(
|
||||||
grid,
|
grid,
|
||||||
(
|
(
|
||||||
pos_inc(upper_left, (1, length * 2 + 3)),
|
pos_inc(upper_left, (0, length)),
|
||||||
set_y(bottom_right, length * 2 + 3 + get_y(upper_left)),
|
set_y(bottom_right, length + 2 + get_y(upper_left)),
|
||||||
),
|
),
|
||||||
context,
|
|
||||||
);
|
);
|
||||||
|
if !self.hide_buttons {
|
||||||
|
self.buttons.draw(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
pos_inc(upper_left, (1, length + 3)),
|
||||||
|
set_y(bottom_right, length + 3 + get_y(upper_left)),
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
clear_area(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
set_y(upper_left, length + 4 + get_y(upper_left)),
|
||||||
|
bottom_right,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
@ -389,6 +415,7 @@ impl Component for FormWidget {
|
|||||||
match *event {
|
match *event {
|
||||||
UIEvent::Input(Key::Up) if self.focus == FormFocus::Buttons => {
|
UIEvent::Input(Key::Up) if self.focus == FormFocus::Buttons => {
|
||||||
self.focus = FormFocus::Fields;
|
self.focus = FormFocus::Fields;
|
||||||
|
self.buttons.set_focus(false);
|
||||||
}
|
}
|
||||||
UIEvent::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => {
|
UIEvent::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => {
|
||||||
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
|
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
|
||||||
@ -406,6 +433,7 @@ impl Component for FormWidget {
|
|||||||
}
|
}
|
||||||
UIEvent::Input(Key::Down) if self.focus == FormFocus::Fields => {
|
UIEvent::Input(Key::Down) if self.focus == FormFocus::Fields => {
|
||||||
self.focus = FormFocus::Buttons;
|
self.focus = FormFocus::Buttons;
|
||||||
|
self.buttons.set_focus(true);
|
||||||
if self.hide_buttons {
|
if self.hide_buttons {
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
return false;
|
return false;
|
||||||
@ -453,10 +481,11 @@ impl Component for FormWidget {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty || self.buttons.is_dirty()
|
||||||
}
|
}
|
||||||
fn set_dirty(&mut self) {
|
fn set_dirty(&mut self) {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
self.buttons.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> ComponentId {
|
fn id(&self) -> ComponentId {
|
||||||
@ -477,6 +506,8 @@ where
|
|||||||
|
|
||||||
result: Option<T>,
|
result: Option<T>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
/// Is the button widget focused, i.e do we need to draw the highlighting?
|
||||||
|
focus: bool,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
}
|
}
|
||||||
@ -500,6 +531,7 @@ where
|
|||||||
buttons: vec![init_val].into_iter().collect(),
|
buttons: vec![init_val].into_iter().collect(),
|
||||||
result: None,
|
result: None,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
|
focus: false,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
}
|
}
|
||||||
@ -513,6 +545,10 @@ where
|
|||||||
pub fn is_resolved(&self) -> bool {
|
pub fn is_resolved(&self) -> bool {
|
||||||
self.result.is_some()
|
self.result.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_focus(&mut self, new_val: bool) {
|
||||||
|
self.focus = new_val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for ButtonWidget<T>
|
impl<T> Component for ButtonWidget<T>
|
||||||
@ -521,6 +557,7 @@ where
|
|||||||
{
|
{
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
|
clear_area(grid, area);
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
|
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
@ -530,12 +567,12 @@ where
|
|||||||
k.as_str(),
|
k.as_str(),
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
if i == self.cursor {
|
if i == self.cursor && self.focus {
|
||||||
Color::Byte(246)
|
Color::Byte(246)
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
},
|
},
|
||||||
Attr::Default,
|
Attr::Bold,
|
||||||
(
|
(
|
||||||
pos_inc(upper_left, (len, 0)),
|
pos_inc(upper_left, (len, 0)),
|
||||||
pos_inc(upper_left, (cur_len + len, 0)),
|
pos_inc(upper_left, (cur_len + len, 0)),
|
||||||
|
Loading…
Reference in New Issue
Block a user