Add scrolling, only redraw dirty areas
parent
8c7a0ae540
commit
d089eb49dc
26
src/bin.rs
26
src/bin.rs
|
@ -141,11 +141,12 @@ fn main() {
|
||||||
|
|
||||||
let mut state = State::new(_stdout, set);
|
let mut state = State::new(_stdout, set);
|
||||||
|
|
||||||
let a = Entity {component: Box::new(AccountMenu::new(&account)) };
|
let menu = Entity {component: Box::new(AccountMenu::new(&account)) };
|
||||||
let listing = MailListing::new(Mailbox::new_dummy());
|
let listing = MailListing::new(Mailbox::new_dummy());
|
||||||
let b = Entity { component: Box::new(listing) };
|
let b = Entity { component: Box::new(listing) };
|
||||||
let window = Entity { component: Box::new(VSplit::new(a,b,90)) };
|
let window = Entity { component: Box::new(VSplit::new(menu,b,90)) };
|
||||||
state.register_entity(window);
|
let status_bar = Entity { component: Box::new(StatusBar::new(window)) };
|
||||||
|
state.register_entity(status_bar);
|
||||||
|
|
||||||
state.render();
|
state.render();
|
||||||
'main: loop {
|
'main: loop {
|
||||||
|
@ -157,7 +158,7 @@ fn main() {
|
||||||
Err(_) => {},
|
Err(_) => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
state.render();
|
state.redraw();
|
||||||
|
|
||||||
'inner: loop {
|
'inner: loop {
|
||||||
match receiver.recv().unwrap() {
|
match receiver.recv().unwrap() {
|
||||||
|
@ -165,19 +166,19 @@ fn main() {
|
||||||
match k {
|
match k {
|
||||||
key @ Key::Char('j') | key @ Key::Char('k') => {
|
key @ Key::Char('j') | key @ Key::Char('k') => {
|
||||||
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
||||||
state.render();
|
state.redraw();
|
||||||
},
|
},
|
||||||
key @ Key::Up | key @ Key::Down => {
|
key @ Key::Up | key @ Key::Down => {
|
||||||
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
||||||
state.render();
|
state.redraw();
|
||||||
}
|
}
|
||||||
Key::Char('\n') => {
|
Key::Char('\n') => {
|
||||||
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))});
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))});
|
||||||
state.render();
|
state.redraw();
|
||||||
}
|
}
|
||||||
Key::Char('i') | Key::Esc => {
|
Key::Char('i') | Key::Esc => {
|
||||||
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)});
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)});
|
||||||
state.render();
|
state.redraw();
|
||||||
}
|
}
|
||||||
Key::F(_) => {
|
Key::F(_) => {
|
||||||
},
|
},
|
||||||
|
@ -210,15 +211,6 @@ fn main() {
|
||||||
eprintln!("got go cmd with {:?}", v);
|
eprintln!("got go cmd with {:?}", v);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
if let Some((inst, 'g')) = cmd_queue.front() {
|
|
||||||
eprintln!("g at front");
|
|
||||||
if (Instant::now - inst >= Duration::from_millis(300)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,19 @@ fn zero_val () -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Settings for the pager function.
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Debug, Deserialize, Default)]
|
||||||
pub struct PagerSettings {
|
pub struct PagerSettings {
|
||||||
#[serde(default = "zero_val")]
|
#[serde(default = "zero_val")]
|
||||||
pager_context: usize,
|
/// Number of context lines when going to next page.
|
||||||
|
/// Default: 0
|
||||||
|
pub pager_context: usize,
|
||||||
|
|
||||||
#[serde(default = "false_val")]
|
#[serde(default = "false_val")]
|
||||||
pager_stop: bool,
|
/// Stop at the end instead of displaying next mail.
|
||||||
|
/// Default: false
|
||||||
|
pub pager_stop: bool,
|
||||||
|
/// Always show headers when scrolling.
|
||||||
|
/// Default: false
|
||||||
|
pub headers_sticky: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ fn make_entry_string(e: &Envelope, idx: usize) -> String {
|
||||||
format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject())
|
format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject())
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_cols: usize = 500;
|
const MAX_COLS: usize = 500;
|
||||||
|
|
||||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
||||||
/// `Pager`.
|
/// `Pager`.
|
||||||
|
@ -34,8 +34,8 @@ impl MailListing {
|
||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
new_cursor_pos: 0,
|
new_cursor_pos: 0,
|
||||||
length: length,
|
length: length,
|
||||||
content: CellBuffer::new(MAX_cols, length+1, Cell::with_char(' ')),
|
content: CellBuffer::new(MAX_COLS, length+1, Cell::with_char(' ')),
|
||||||
dirty: false,
|
dirty: true,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
mailbox: mailbox,
|
mailbox: mailbox,
|
||||||
pager: None,
|
pager: None,
|
||||||
|
@ -46,10 +46,19 @@ impl MailListing {
|
||||||
|
|
||||||
impl MailListing {
|
impl MailListing {
|
||||||
/// Draw only the list of `Envelope`s.
|
/// Draw only the list of `Envelope`s.
|
||||||
fn draw_list(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
clear_area(grid, upper_left, bottom_right);
|
clear_area(grid, area);
|
||||||
write_string_to_grid(&format!("Folder `{}` is empty.", self.mailbox.folder.get_name()), grid, Color::Default, Color::Default, upper_left, upper_left);
|
let new_area = (upper_left, set_x(upper_left, get_x(bottom_right)));
|
||||||
|
write_string_to_grid(&format!("Folder `{}` is empty.",
|
||||||
|
self.mailbox.folder.get_name()),
|
||||||
|
grid,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
new_area);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||||
|
@ -64,12 +73,35 @@ impl MailListing {
|
||||||
if *idx >= self.length {
|
if *idx >= self.length {
|
||||||
continue; //bounds check
|
continue; //bounds check
|
||||||
}
|
}
|
||||||
let color = if self.cursor_pos == *idx { if *idx % 2 == 0 { Color::Byte(236) } else {Color::Default } } else { Color::Byte(246) };
|
let envelope: &Envelope = &self.mailbox.collection[*idx];
|
||||||
let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[*idx], *idx), grid, Color::Default, color, set_y(upper_left, get_y(upper_left)+(*idx % rows)), bottom_right);
|
|
||||||
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if self.cursor_pos == *idx {
|
||||||
|
if !envelope.is_seen() {
|
||||||
|
Color::Byte(252)
|
||||||
|
} else if *idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color::Byte(246)
|
||||||
|
};
|
||||||
|
let new_area = (set_y(upper_left, get_y(upper_left)+(*idx % rows)), bottom_right);
|
||||||
|
let x = write_string_to_grid(&make_entry_string(envelope, *idx),
|
||||||
|
grid,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
new_area);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x,get_y(upper_left)+(*idx % rows))].set_ch(' ');
|
grid[(x,get_y(upper_left)+(*idx % rows))].set_ch(' ');
|
||||||
grid[(x,get_y(upper_left)+(*idx % rows))].set_bg(color);
|
grid[(x,get_y(upper_left)+(*idx % rows))].set_bg(bg_color);
|
||||||
}
|
}
|
||||||
|
context.dirty_areas.push_back(new_area);
|
||||||
}
|
}
|
||||||
self.cursor_pos = self.new_cursor_pos;
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
return;
|
return;
|
||||||
|
@ -77,28 +109,42 @@ impl MailListing {
|
||||||
self.cursor_pos = self.new_cursor_pos;
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
let mut idx = page_no*rows;
|
let mut idx = page_no*rows;
|
||||||
for y in get_y(upper_left)..=get_y(bottom_right) {
|
for y in get_y(upper_left)..=get_y(bottom_right) {
|
||||||
if idx >= self.length {
|
if idx >= self.length {
|
||||||
clear_area(grid, set_y(upper_left, y), bottom_right);
|
clear_area(grid,
|
||||||
|
(set_y(upper_left, y), bottom_right));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Write an entire line for each envelope entry. */
|
/* Write an entire line for each envelope entry. */
|
||||||
|
let envelope: &Envelope = &self.mailbox.collection[idx];
|
||||||
|
|
||||||
let color = if self.cursor_pos == idx {
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if self.cursor_pos == idx {
|
||||||
Color::Byte(246)
|
Color::Byte(246)
|
||||||
} else {
|
} else {
|
||||||
if idx % 2 == 0 {
|
if !envelope.is_seen() {
|
||||||
Color::Byte(236)
|
Color::Byte(251)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[idx], idx), grid, Color::Default, color, set_y(upper_left, y), bottom_right);
|
let x = write_string_to_grid(&make_entry_string(envelope, idx),
|
||||||
|
grid,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
(set_y(upper_left, y), bottom_right));
|
||||||
|
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x,y)].set_ch(' ');
|
grid[(x,y)].set_ch(' ');
|
||||||
grid[(x,y)].set_bg(color);
|
grid[(x,y)].set_bg(bg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
idx+=1;
|
idx+=1;
|
||||||
|
@ -106,30 +152,39 @@ impl MailListing {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a pager for the `Envelope` currently under the cursor.
|
/// Create a pager for the `Envelope` currently under the cursor.
|
||||||
fn draw_mail_view(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
let ref mail = self.mailbox.collection[self.cursor_pos];
|
let ref mail = self.mailbox.collection[self.cursor_pos];
|
||||||
|
|
||||||
let rows = get_y(bottom_right) - get_y(upper_left);
|
let rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
let cols = get_x(bottom_right) - get_x(upper_left);
|
let cols = get_x(bottom_right) - get_x(upper_left);
|
||||||
|
|
||||||
self.pager = Some(Pager::new(mail, rows, cols));
|
self.pager = Some(Pager::new(mail, rows, cols));
|
||||||
let pager = self.pager.as_mut().unwrap();
|
self.pager.as_mut().map(|p| p.draw(grid, area, context));
|
||||||
pager.draw(grid, upper_left,bottom_right);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for MailListing {
|
impl Component for MailListing {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
if !self.unfocused {
|
if !self.unfocused {
|
||||||
if !self.dirty {
|
if !self.dirty {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
/* Draw the entire list */
|
/* Draw the entire list */
|
||||||
self.draw_list(grid, upper_left,bottom_right);
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
self.draw_list(grid,
|
||||||
|
area,
|
||||||
|
context);
|
||||||
} else {
|
} else {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
if self.length == 0 && self.dirty {
|
if self.length == 0 && self.dirty {
|
||||||
clear_area(grid, upper_left, bottom_right);
|
clear_area(grid, area);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
/* Render the mail body in a pager, basically copy what HSplit does */
|
/* Render the mail body in a pager, basically copy what HSplit does */
|
||||||
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
@ -139,12 +194,16 @@ impl Component for MailListing {
|
||||||
|
|
||||||
if !self.dirty {
|
if !self.dirty {
|
||||||
if let Some(ref mut p) = self.pager {
|
if let Some(ref mut p) = self.pager {
|
||||||
p.draw(grid, (get_x(upper_left), get_y(upper_left) + mid+6), bottom_right);
|
p.draw(grid,
|
||||||
|
((get_x(upper_left), get_y(upper_left) + mid+6), bottom_right),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
self.draw_list(grid, upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-3));
|
self.draw_list(grid,
|
||||||
|
(upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-1)),
|
||||||
|
context);
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -166,44 +225,68 @@ impl Component for MailListing {
|
||||||
{
|
{
|
||||||
let ref mail = self.mailbox.collection[self.cursor_pos];
|
let ref mail = self.mailbox.collection[self.cursor_pos];
|
||||||
|
|
||||||
let x = write_string_to_grid(&format!("Date: {}", mail.get_date_as_str()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+1), set_y(upper_left, mid+1));
|
eprintln!("writing headers {} {}", mail.get_date_as_str(), mail.get_subject());
|
||||||
|
let x = write_string_to_grid(&format!("Date: {}", mail.get_date_as_str()),
|
||||||
|
grid,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
|
||||||
for x in x..get_x(bottom_right) {
|
for x in x..get_x(bottom_right) {
|
||||||
grid[(x, mid+1)].set_ch(' ');
|
grid[(x, mid+1)].set_ch(' ');
|
||||||
grid[(x, mid+1)].set_bg(Color::Default);
|
grid[(x, mid+1)].set_bg(Color::Default);
|
||||||
grid[(x, mid+1)].set_fg(Color::Default);
|
grid[(x, mid+1)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("From: {}", mail.get_from()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+2), set_y(upper_left, mid+2));
|
let x = write_string_to_grid(&format!("From: {}", mail.get_from()),
|
||||||
|
grid,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, mid+2), set_y(bottom_right, mid+2)));
|
||||||
for x in x..get_x(bottom_right) {
|
for x in x..get_x(bottom_right) {
|
||||||
grid[(x, mid+2)].set_ch(' ');
|
grid[(x, mid+2)].set_ch(' ');
|
||||||
grid[(x, mid+2)].set_bg(Color::Default);
|
grid[(x, mid+2)].set_bg(Color::Default);
|
||||||
grid[(x, mid+2)].set_fg(Color::Default);
|
grid[(x, mid+2)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("To: {}", mail.get_to()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+3), set_y(upper_left, mid+3));
|
let x = write_string_to_grid(&format!("To: {}", mail.get_to()),
|
||||||
|
grid,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, mid+3), set_y(bottom_right, mid+3)));
|
||||||
for x in x..get_x(bottom_right) {
|
for x in x..get_x(bottom_right) {
|
||||||
grid[(x, mid+3)].set_ch(' ');
|
grid[(x, mid+3)].set_ch(' ');
|
||||||
grid[(x, mid+3)].set_bg(Color::Default);
|
grid[(x, mid+3)].set_bg(Color::Default);
|
||||||
grid[(x, mid+3)].set_fg(Color::Default);
|
grid[(x, mid+3)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("Subject: {}", mail.get_subject()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+4), set_y(upper_left, mid+4));
|
let x = write_string_to_grid(&format!("Subject: {}", mail.get_subject()),
|
||||||
|
grid,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, mid+4), set_y(bottom_right, mid+4)));
|
||||||
for x in x..get_x(bottom_right) {
|
for x in x..get_x(bottom_right) {
|
||||||
grid[(x, mid+4)].set_ch(' ');
|
grid[(x, mid+4)].set_ch(' ');
|
||||||
grid[(x, mid+4)].set_bg(Color::Default);
|
grid[(x, mid+4)].set_bg(Color::Default);
|
||||||
grid[(x, mid+4)].set_fg(Color::Default);
|
grid[(x, mid+4)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("Message-ID: {}", mail.get_message_id_raw()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+5), set_y(upper_left, mid+5));
|
let x = write_string_to_grid(&format!("Message-ID: {}", mail.get_message_id_raw()),
|
||||||
|
grid,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, mid+5), set_y(bottom_right, mid+5)));
|
||||||
for x in x..get_x(bottom_right) {
|
for x in x..get_x(bottom_right) {
|
||||||
grid[(x, mid+5)].set_ch(' ');
|
grid[(x, mid+5)].set_ch(' ');
|
||||||
grid[(x, mid+5)].set_bg(Color::Default);
|
grid[(x, mid+5)].set_bg(Color::Default);
|
||||||
grid[(x, mid+5)].set_fg(Color::Default);
|
grid[(x, mid+5)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, mid+5)));
|
||||||
|
|
||||||
/* Draw body */
|
/* Draw body */
|
||||||
self.draw_mail_view(grid, (get_x(upper_left), get_y(upper_left) + mid + headers_rows), bottom_right);
|
self.draw_mail_view(grid,
|
||||||
|
((get_x(upper_left), get_y(upper_left) + mid + headers_rows), bottom_right),
|
||||||
|
context);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::Input(Key::Up) => {
|
UIEventType::Input(Key::Up) => {
|
||||||
if self.cursor_pos > 0 {
|
if self.cursor_pos > 0 {
|
||||||
|
@ -240,9 +323,12 @@ impl Component for MailListing {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if let Some(ref mut p) = self.pager {
|
if let Some(ref mut p) = self.pager {
|
||||||
p.process_event(event, queue);
|
p.process_event(event, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -274,10 +360,12 @@ impl AccountMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for AccountMenu {
|
impl Component for AccountMenu {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
if !(self.dirty) {
|
if !(self.dirty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
let mut parents: Vec<Option<usize>> = vec!(None; self.entries.len());
|
let mut parents: Vec<Option<usize>> = vec!(None; self.entries.len());
|
||||||
|
|
||||||
|
@ -340,11 +428,16 @@ impl Component for AccountMenu {
|
||||||
} else {
|
} else {
|
||||||
format!("{}", lines[idx])
|
format!("{}", lines[idx])
|
||||||
};
|
};
|
||||||
write_string_to_grid(&s, grid, Color::Byte(30), Color::Default, set_y(upper_left, y), bottom_right);
|
write_string_to_grid(&s,
|
||||||
|
grid,
|
||||||
|
Color::Byte(30),
|
||||||
|
Color::Default,
|
||||||
|
(set_y(upper_left, y), bottom_right));
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::RefreshMailbox(ref m) => {
|
UIEventType::RefreshMailbox(ref m) => {
|
||||||
self.highlight_folder(m);
|
self.highlight_folder(m);
|
||||||
|
@ -353,4 +446,7 @@ impl Component for AccountMenu {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
use super::*;
|
|
||||||
|
|
||||||
|
use super::*;
|
||||||
pub use utilities::*;
|
pub use utilities::*;
|
||||||
pub use mail::*;
|
pub use mail::*;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ use std::fmt;
|
||||||
|
|
||||||
|
|
||||||
use super::cells::{Color, CellBuffer};
|
use super::cells::{Color, CellBuffer};
|
||||||
use super::position::Pos;
|
use super::position::{Area, };
|
||||||
use super::{UIEvent, UIEventType, Key};
|
use super::{UIEvent, UIEventType, Key};
|
||||||
|
|
||||||
/// The upper and lower boundary char.
|
/// The upper and lower boundary char.
|
||||||
|
@ -58,14 +58,14 @@ const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
||||||
/// `Entity` is a container for Components. Totally useless now so if it is not useful in the
|
/// `Entity` is a container for Components. Totally useless now so if it is not useful in the
|
||||||
/// future (ie hold some information, id or state) it should be removed.
|
/// future (ie hold some information, id or state) it should be removed.
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
//queue: VecDeque,
|
//context: VecDeque,
|
||||||
pub component: Box<Component>, // more than one?
|
pub component: Box<Component>, // more than one?
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity {
|
impl Entity {
|
||||||
/// Pass events to child component.
|
/// Pass events to child component.
|
||||||
pub fn rcv_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
self.component.process_event(&event, queue);
|
self.component.process_event(&event, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,22 +79,30 @@ impl fmt::Debug for Entity {
|
||||||
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
|
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
|
||||||
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
|
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
|
||||||
pub trait Component {
|
pub trait Component {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos);
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||||
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>);
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context);
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, upper_left: Pos, bottom_right: Pos) -> usize {
|
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area) -> usize {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
let (mut x, mut y) = upper_left;
|
let (mut x, mut y) = upper_left;
|
||||||
|
if y > (get_y(bottom_right)) || x > get_x(bottom_right) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
for c in s.chars() {
|
for c in s.chars() {
|
||||||
grid[(x,y)].set_ch(c);
|
grid[(x,y)].set_ch(c);
|
||||||
grid[(x,y)].set_fg(fg_color);
|
grid[(x,y)].set_fg(fg_color);
|
||||||
grid[(x,y)].set_bg(bg_color);
|
grid[(x,y)].set_bg(bg_color);
|
||||||
x += 1;
|
x += 1;
|
||||||
if x == (get_x(bottom_right)) {
|
if x == (get_x(bottom_right))+1 {
|
||||||
x = get_x(upper_left);
|
x = get_x(upper_left);
|
||||||
y += 1;
|
y += 1;
|
||||||
if y == (get_y(bottom_right)) {
|
if y == (get_y(bottom_right))+1 {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +110,9 @@ fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_colo
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_area(grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn clear_area(grid: &mut CellBuffer, area: Area) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
for y in get_y(upper_left)..=get_y(bottom_right) {
|
for y in get_y(upper_left)..=get_y(bottom_right) {
|
||||||
for x in get_x(upper_left)..=get_x(bottom_right) {
|
for x in get_x(upper_left)..=get_x(bottom_right) {
|
||||||
grid[(x,y)].set_ch(' ');
|
grid[(x,y)].set_ch(' ');
|
||||||
|
|
|
@ -5,7 +5,9 @@ pub struct BoxPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for BoxPanel {
|
impl Component for BoxPanel {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
grid[upper_left].set_ch('u');
|
grid[upper_left].set_ch('u');
|
||||||
grid[bottom_right].set_ch('b');
|
grid[bottom_right].set_ch('b');
|
||||||
let width = get_x(bottom_right) - get_x(upper_left);
|
let width = get_x(bottom_right) - get_x(upper_left);
|
||||||
|
@ -24,7 +26,7 @@ impl Component for BoxPanel {
|
||||||
grid[(i, get_y(upper_left) + height)].set_ch('─');
|
grid[(i, get_y(upper_left) + height)].set_ch('─');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +50,9 @@ impl HSplit {
|
||||||
|
|
||||||
|
|
||||||
impl Component for HSplit {
|
impl Component for HSplit {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
let bottom_entity_height = (self.ratio*total_rows )/100;
|
let bottom_entity_height = (self.ratio*total_rows )/100;
|
||||||
let mid = get_y(upper_left) + total_rows - bottom_entity_height;
|
let mid = get_y(upper_left) + total_rows - bottom_entity_height;
|
||||||
|
@ -56,13 +60,20 @@ impl Component for HSplit {
|
||||||
for i in get_x(upper_left)..=get_x(bottom_right) {
|
for i in get_x(upper_left)..=get_x(bottom_right) {
|
||||||
grid[(i, mid)].set_ch('─');
|
grid[(i, mid)].set_ch('─');
|
||||||
}
|
}
|
||||||
let _ = self.top.component.draw(grid, upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1));
|
let _ = self.top.component.draw(grid,
|
||||||
let _ = self.bottom.component.draw(grid, (get_x(upper_left), get_y(upper_left) + mid), bottom_right);
|
(upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1)),
|
||||||
|
context);
|
||||||
|
let _ = self.bottom.component.draw(grid,
|
||||||
|
((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
|
||||||
|
context);
|
||||||
grid[bottom_right].set_ch('b');
|
grid[bottom_right].set_ch('b');
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
self.top.rcv_event(event, queue);
|
self.top.rcv_event(event, context);
|
||||||
self.bottom.rcv_event(event, queue);
|
self.bottom.rcv_event(event, context);
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.top.component.is_dirty() || self.bottom.component.is_dirty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +97,9 @@ impl VSplit {
|
||||||
|
|
||||||
|
|
||||||
impl Component for VSplit {
|
impl Component for VSplit {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
let total_cols = get_x(bottom_right) - get_x(upper_left);
|
let total_cols = get_x(bottom_right) - get_x(upper_left);
|
||||||
let right_entity_width = (self.ratio*total_cols )/100;
|
let right_entity_width = (self.ratio*total_cols )/100;
|
||||||
let mid = get_x(bottom_right) - right_entity_width;
|
let mid = get_x(bottom_right) - right_entity_width;
|
||||||
|
@ -101,7 +114,7 @@ impl Component for VSplit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in get_y(upper_left)..get_y(bottom_right) {
|
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||||
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
||||||
}
|
}
|
||||||
if get_y(bottom_right)> 1 {
|
if get_y(bottom_right)> 1 {
|
||||||
|
@ -113,12 +126,19 @@ impl Component for VSplit {
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = self.left.component.draw(grid, upper_left, (mid-1, get_y(bottom_right)));
|
let _ = self.left.component.draw(grid,
|
||||||
let _ = self.right.component.draw(grid, (mid+1, get_y(upper_left)), bottom_right);
|
(upper_left, (mid-1, get_y(bottom_right))),
|
||||||
|
context);
|
||||||
|
let _ = self.right.component.draw(grid,
|
||||||
|
((mid+1, get_y(upper_left)), bottom_right),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
self.left.rcv_event(event, queue);
|
self.left.rcv_event(event, context);
|
||||||
self.right.rcv_event(event, queue);
|
self.right.rcv_event(event, context);
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.left.component.is_dirty() || self.right.component.is_dirty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +156,9 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for TextBox {
|
impl Component for TextBox {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
let mut x = get_x(upper_left);
|
let mut x = get_x(upper_left);
|
||||||
let y = get_y(upper_left);
|
let y = get_y(upper_left);
|
||||||
for c in self.content.chars() {
|
for c in self.content.chars() {
|
||||||
|
@ -151,7 +173,7 @@ impl Component for TextBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,27 +183,31 @@ impl Component for TextBox {
|
||||||
/// current view of the text. It is responsible for scrolling etc.
|
/// current view of the text. It is responsible for scrolling etc.
|
||||||
pub struct Pager {
|
pub struct Pager {
|
||||||
cursor_pos: usize,
|
cursor_pos: usize,
|
||||||
|
lines_no: usize,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
cols: usize,
|
cols: usize,
|
||||||
height: usize,
|
dirty: bool,
|
||||||
pub dirty: bool,
|
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn new(mail: &Envelope, height: usize, width: usize) -> Self {
|
pub fn new(mail: &Envelope, rows: usize, cols: usize) -> Self {
|
||||||
let text = mail.get_body().get_text();
|
let text = mail.get_body().get_text();
|
||||||
let lines: Vec<&str> = text.trim().split('\n').collect();
|
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||||
let rows = lines.len();
|
let lines_no = lines.len();
|
||||||
let mut content = CellBuffer::new(width, rows, Cell::with_char(' '));
|
let mut content = CellBuffer::new(cols, rows, Cell::with_char(' '));
|
||||||
for (i, l) in lines.iter().enumerate() {
|
for (i, l) in lines.iter().enumerate() {
|
||||||
write_string_to_grid(l, &mut content, Color::Default, Color::Default, (0,i), (width-1, rows-1));
|
write_string_to_grid(l,
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((0, i), (cols-1, rows-1)));
|
||||||
}
|
}
|
||||||
Pager {
|
Pager {
|
||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
|
lines_no: lines_no,
|
||||||
rows: rows,
|
rows: rows,
|
||||||
height: height,
|
cols: cols,
|
||||||
cols: width,
|
|
||||||
dirty: true,
|
dirty: true,
|
||||||
content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
|
@ -189,32 +215,44 @@ impl Pager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Pager {
|
impl Component for Pager {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
if !self.dirty {
|
if !self.dirty {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clear_area(grid, (get_x(upper_left), get_y(upper_left)-1), bottom_right);
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
clear_area(grid,
|
||||||
|
((get_x(upper_left), get_y(upper_left)-1), bottom_right));
|
||||||
|
context.dirty_areas.push_back(((get_x(upper_left), get_y(upper_left)-1), bottom_right));
|
||||||
|
|
||||||
|
if self.lines_no == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pager_context = context.settings.pager.pager_context;
|
||||||
|
let rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
let page_length = rows / self.lines_no;
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
let mut inner_x = 0;
|
let mut inner_x = 0;
|
||||||
let mut inner_y = self.cursor_pos;
|
let mut inner_y = self.cursor_pos;
|
||||||
|
|
||||||
//if self.cursor_pos < self.rows && self.rows - self.cursor_pos > self.height {
|
for y in get_y(upper_left)..=get_y(bottom_right) {
|
||||||
for y in get_y(upper_left)..get_y(bottom_right) {
|
'for_x: for x in get_x(upper_left)..=get_x(bottom_right) {
|
||||||
'for_x: for x in get_x(upper_left)..get_x(bottom_right) {
|
if inner_x == self.cols {
|
||||||
if inner_x == self.cols {
|
break 'for_x;
|
||||||
break 'for_x;
|
|
||||||
}
|
|
||||||
grid[(x,y)] = self.content[(inner_x, inner_y)];
|
|
||||||
inner_x += 1;
|
|
||||||
}
|
|
||||||
inner_y += 1;
|
|
||||||
inner_x = 0;
|
|
||||||
if inner_y == self.rows {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
grid[(x,y)] = self.content[(inner_x, inner_y)];
|
||||||
|
inner_x += 1;
|
||||||
}
|
}
|
||||||
|
inner_y += 1;
|
||||||
|
inner_x = 0;
|
||||||
|
if inner_y == self.rows {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::Input(Key::Char('k')) => {
|
UIEventType::Input(Key::Char('k')) => {
|
||||||
if self.cursor_pos > 0 {
|
if self.cursor_pos > 0 {
|
||||||
|
@ -232,5 +270,70 @@ impl Component for Pager {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status bar.
|
||||||
|
pub struct StatusBar {
|
||||||
|
container: Entity,
|
||||||
|
position: usize,
|
||||||
|
status: String,
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar {
|
||||||
|
pub fn new(container: Entity) -> Self {
|
||||||
|
StatusBar {
|
||||||
|
container: container,
|
||||||
|
position: 0,
|
||||||
|
status: String::with_capacity(250),
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if !self.dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
clear_area(grid, area);
|
||||||
|
let x = write_string_to_grid(&self.status,
|
||||||
|
grid,
|
||||||
|
Color::Byte(36),
|
||||||
|
Color::Default,
|
||||||
|
area);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Component for StatusBar {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
if total_rows == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.container.component.draw(grid,
|
||||||
|
(upper_left, (get_x(bottom_right), get_y(bottom_right)-1)),
|
||||||
|
context);
|
||||||
|
self.draw_status_bar(grid, (set_y(upper_left, get_y(bottom_right)), bottom_right), context);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
|
self.container.rcv_event(event, context);
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::RefreshMailbox(ref m) => {
|
||||||
|
self.status = format!("Mailbox: {}, Messages: {}, New: {}", m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count());
|
||||||
|
self.dirty = true;
|
||||||
|
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty || self.container.component.is_dirty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
197
src/ui/lib.rs
197
src/ui/lib.rs
|
@ -1,197 +0,0 @@
|
||||||
extern crate termion;
|
|
||||||
|
|
||||||
|
|
||||||
use termion::{clear, cursor};
|
|
||||||
use termion::raw::IntoRawMode;
|
|
||||||
use termion::event::{Key as TermionKey, Event as TermionEvent, MouseEvent as TermionMouseEvent};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//use std::env;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use termion::input::TermRead;
|
|
||||||
use std::io::{stdout, stdin, stderr};
|
|
||||||
//use std::collections::VecDeque;
|
|
||||||
//use std::process;
|
|
||||||
|
|
||||||
|
|
||||||
mod components;
|
|
||||||
mod position;
|
|
||||||
mod cells;
|
|
||||||
use cells::{Cell, CellBuffer};
|
|
||||||
use position::Pos;
|
|
||||||
|
|
||||||
pub use self::components::*;
|
|
||||||
pub use self::position::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UIEventType {
|
|
||||||
Input(Key),
|
|
||||||
RefreshMailbox(String),
|
|
||||||
//Quit?
|
|
||||||
Resize,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UIEvent {
|
|
||||||
pub id: u64,
|
|
||||||
pub event_type: UIEventType,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct State<W: Write> {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
|
|
||||||
grid: CellBuffer,
|
|
||||||
pub stdout: termion::raw::RawTerminal<W>,
|
|
||||||
entities: Vec<Entity>,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> State<W> {
|
|
||||||
pub fn new(stdout: W) -> Self {
|
|
||||||
let termsize = termion::terminal_size().ok();
|
|
||||||
let termwidth = termsize.map(|(w,_)| w);
|
|
||||||
let termheight = termsize.map(|(_,h)| h);
|
|
||||||
let width = termwidth.unwrap_or(0) as usize;
|
|
||||||
let height = termheight.unwrap_or(0) as usize;
|
|
||||||
let mut s = State {
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
//queue: VecDeque::new();
|
|
||||||
|
|
||||||
grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')),
|
|
||||||
stdout: stdout.into_raw_mode().unwrap(),
|
|
||||||
entities: Vec::with_capacity(2),
|
|
||||||
};
|
|
||||||
write!(s.stdout, "{}{}", clear::All, cursor::Goto(1,1)).unwrap();
|
|
||||||
s
|
|
||||||
}
|
|
||||||
pub fn hello_w(&mut self) {
|
|
||||||
write!(self.stdout, "Hey there.").unwrap();
|
|
||||||
}
|
|
||||||
fn update_size(&mut self) {
|
|
||||||
/* update dimensions. TODO: Only do that in size change events. ie SIGWINCH */
|
|
||||||
let termsize = termion::terminal_size().ok();
|
|
||||||
let termwidth = termsize.map(|(w,_)| w);
|
|
||||||
let termheight = termsize.map(|(_,h)| h);
|
|
||||||
self.width = termwidth.unwrap_or(72) as usize;
|
|
||||||
self.height = termheight.unwrap_or(120) as usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&mut self) {
|
|
||||||
self.update_size();
|
|
||||||
|
|
||||||
/* draw each entity */ for i in 0..self.entities.len() {
|
|
||||||
self.draw_entity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
for y in 1..self.height {
|
|
||||||
write!(self.stdout, "{}", cursor::Goto(1,y as u16)).unwrap();
|
|
||||||
for x in 1..self.width {
|
|
||||||
let c = self.grid[(x,y)];
|
|
||||||
if c.get_bg() == cells::Color::Default {
|
|
||||||
write!(self.stdout, "{}",c.ch()).unwrap();
|
|
||||||
} else {
|
|
||||||
write!(self.stdout, "{}{}{}", termion::color::Bg(termion::color::LightBlack),c.ch(),termion::color::Bg(termion::color::Reset)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn draw_entity(&mut self, idx: usize) {
|
|
||||||
let ref mut entity = self.entities[idx];
|
|
||||||
eprintln!("Entity is {:?}", entity);
|
|
||||||
let upper_left = (1,1);
|
|
||||||
let bottom_right = (self.width, self.height);
|
|
||||||
eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right);
|
|
||||||
|
|
||||||
entity.component.draw(upper_left, bottom_right, &mut self.grid);
|
|
||||||
}
|
|
||||||
pub fn register_entity(&mut self, entity: Entity) {
|
|
||||||
self.entities.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rcv_event(&mut self, event: UIEvent) {
|
|
||||||
/* inform each entity */ for i in 0..self.entities.len() {
|
|
||||||
self.entities[i].rcv_event(&event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_key(k: TermionKey ) -> Key {
|
|
||||||
match k {
|
|
||||||
TermionKey::Backspace => Key::Backspace,
|
|
||||||
TermionKey::Left => Key::Left,
|
|
||||||
TermionKey::Right => Key::Right,
|
|
||||||
TermionKey::Up => Key::Up,
|
|
||||||
TermionKey::Down => Key::Down,
|
|
||||||
TermionKey::Home => Key::Home,
|
|
||||||
TermionKey::End => Key::End,
|
|
||||||
TermionKey::PageUp => Key::PageUp,
|
|
||||||
TermionKey::PageDown => Key::PageDown,
|
|
||||||
TermionKey::Delete => Key::Delete,
|
|
||||||
TermionKey::Insert => Key::Insert,
|
|
||||||
TermionKey::F(u) => Key::F(u),
|
|
||||||
TermionKey::Char(c) => Key::Char(c),
|
|
||||||
TermionKey::Alt(c) => Key::Alt(c),
|
|
||||||
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
|
||||||
TermionKey::Null => Key::Null,
|
|
||||||
TermionKey::Esc => Key::Esc,
|
|
||||||
_ => Key::Char(' '),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Key {
|
|
||||||
/// Backspace.
|
|
||||||
Backspace,
|
|
||||||
/// Left arrow.
|
|
||||||
Left,
|
|
||||||
/// Right arrow.
|
|
||||||
Right,
|
|
||||||
/// Up arrow.
|
|
||||||
Up,
|
|
||||||
/// Down arrow.
|
|
||||||
Down,
|
|
||||||
/// Home key.
|
|
||||||
Home,
|
|
||||||
/// End key.
|
|
||||||
End,
|
|
||||||
/// Page Up key.
|
|
||||||
PageUp,
|
|
||||||
/// Page Down key.
|
|
||||||
PageDown,
|
|
||||||
/// Delete key.
|
|
||||||
Delete,
|
|
||||||
/// Insert key.
|
|
||||||
Insert,
|
|
||||||
/// Function keys.
|
|
||||||
///
|
|
||||||
/// Only function keys 1 through 12 are supported.
|
|
||||||
F(u8),
|
|
||||||
/// Normal character.
|
|
||||||
Char(char),
|
|
||||||
/// Alt modified character.
|
|
||||||
Alt(char),
|
|
||||||
/// Ctrl modified character.
|
|
||||||
///
|
|
||||||
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
|
|
||||||
Ctrl(char),
|
|
||||||
/// Null byte.
|
|
||||||
Null,
|
|
||||||
/// Esc key.
|
|
||||||
Esc,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_events<F>(stdin: std::io::Stdin, closure: F) where F: Fn(Key) -> (){
|
|
||||||
let stdin = stdin.lock();
|
|
||||||
for c in stdin.keys() {
|
|
||||||
if let Ok(k) = c {
|
|
||||||
let k = convert_key(k);
|
|
||||||
eprintln!("Received key: {:?}", k);
|
|
||||||
closure(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,8 +19,9 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub mod components;
|
#[macro_use]
|
||||||
pub mod position;
|
pub mod position;
|
||||||
|
pub mod components;
|
||||||
pub mod cells;
|
pub mod cells;
|
||||||
|
|
||||||
extern crate termion;
|
extern crate termion;
|
||||||
|
@ -28,6 +29,7 @@ extern crate ncurses;
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
pub use self::position::*;
|
||||||
|
|
||||||
/* Color pairs; foreground && background. */
|
/* Color pairs; foreground && background. */
|
||||||
/// Default color.
|
/// Default color.
|
||||||
|
@ -87,7 +89,6 @@ use termion::input::TermRead;
|
||||||
|
|
||||||
use self::cells::*;
|
use self::cells::*;
|
||||||
pub use self::components::*;
|
pub use self::components::*;
|
||||||
pub use self::position::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UIEventType {
|
pub enum UIEventType {
|
||||||
|
@ -105,6 +106,14 @@ pub struct UIEvent {
|
||||||
pub event_type: UIEventType,
|
pub event_type: UIEventType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
settings: Settings,
|
||||||
|
queue: VecDeque<UIEvent>,
|
||||||
|
/// Areas of the screen that must be redrawn in the next render
|
||||||
|
dirty_areas: VecDeque<Area>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub struct State<W: Write> {
|
pub struct State<W: Write> {
|
||||||
cols: usize,
|
cols: usize,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
|
@ -112,8 +121,7 @@ pub struct State<W: Write> {
|
||||||
grid: CellBuffer,
|
grid: CellBuffer,
|
||||||
stdout: termion::raw::RawTerminal<W>,
|
stdout: termion::raw::RawTerminal<W>,
|
||||||
entities: Vec<Entity>,
|
entities: Vec<Entity>,
|
||||||
settings: Settings,
|
context: Context,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> Drop for State<W> {
|
impl<W: Write> Drop for State<W> {
|
||||||
|
@ -133,12 +141,15 @@ impl<W: Write> State<W> {
|
||||||
let mut s = State {
|
let mut s = State {
|
||||||
cols: cols,
|
cols: cols,
|
||||||
rows: rows,
|
rows: rows,
|
||||||
//queue: VecDeque::new();
|
|
||||||
|
|
||||||
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
||||||
stdout: stdout.into_raw_mode().unwrap(),
|
stdout: stdout.into_raw_mode().unwrap(),
|
||||||
entities: Vec::with_capacity(1),
|
entities: Vec::with_capacity(1),
|
||||||
settings: settings,
|
|
||||||
|
context: Context {
|
||||||
|
settings: settings,
|
||||||
|
queue: VecDeque::with_capacity(5),
|
||||||
|
dirty_areas: VecDeque::with_capacity(5),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap();
|
write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap();
|
||||||
s
|
s
|
||||||
|
@ -157,17 +168,23 @@ impl<W: Write> State<W> {
|
||||||
self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
|
self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self) {
|
pub fn redraw(&mut self) {
|
||||||
self.update_size();
|
for i in 0..self.entities.len() {
|
||||||
|
|
||||||
/* draw each entity */ for i in 0..self.entities.len() {
|
|
||||||
self.draw_entity(i);
|
self.draw_entity(i);
|
||||||
}
|
}
|
||||||
|
let areas: Vec<Area> = self.context.dirty_areas.drain(0..).collect();
|
||||||
|
/* draw each entity */
|
||||||
|
for a in areas {
|
||||||
|
self.draw_area(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn draw_area(&mut self, area: Area) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
/* Only draw dirty areas */
|
for y in get_y(upper_left)..=get_y(bottom_right) {
|
||||||
for y in 0..self.rows {
|
write!(self.stdout, "{}", cursor::Goto(get_x(upper_left) as u16 + 1,(y+1) as u16)).unwrap();
|
||||||
write!(self.stdout, "{}", cursor::Goto(1,(y+1) as u16)).unwrap();
|
for x in get_x(upper_left)..=get_x(bottom_right) {
|
||||||
for x in 0..self.cols {
|
|
||||||
let c = self.grid[(x,y)];
|
let c = self.grid[(x,y)];
|
||||||
|
|
||||||
if c.get_bg() != cells::Color::Default {
|
if c.get_bg() != cells::Color::Default {
|
||||||
|
@ -188,22 +205,39 @@ impl<W: Write> State<W> {
|
||||||
}
|
}
|
||||||
self.stdout.flush().unwrap();
|
self.stdout.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
pub fn render(&mut self) {
|
||||||
|
self.update_size();
|
||||||
|
|
||||||
|
/* draw each entity */
|
||||||
|
for i in 0..self.entities.len() {
|
||||||
|
self.draw_entity(i);
|
||||||
|
}
|
||||||
|
let cols = self.cols;
|
||||||
|
let rows = self.rows;
|
||||||
|
|
||||||
|
/* Only draw dirty areas */
|
||||||
|
self.draw_area(((0, 0), (cols-1, rows-1)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
pub fn draw_entity(&mut self, idx: usize) {
|
pub fn draw_entity(&mut self, idx: usize) {
|
||||||
let ref mut entity = self.entities[idx];
|
let entity = &mut self.entities[idx];
|
||||||
let upper_left = (0,0);
|
let upper_left = (0,0);
|
||||||
let bottom_right = (self.cols-1, self.rows-1);
|
let bottom_right = (self.cols-1, self.rows-1);
|
||||||
|
|
||||||
entity.component.draw(&mut self.grid, upper_left, bottom_right);
|
if entity.component.is_dirty() {
|
||||||
|
entity.component.draw(&mut self.grid,
|
||||||
|
(upper_left, bottom_right),
|
||||||
|
&mut self.context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn register_entity(&mut self, entity: Entity) {
|
pub fn register_entity(&mut self, entity: Entity) {
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rcv_event(&mut self, event: UIEvent) {
|
pub fn rcv_event(&mut self, event: UIEvent) {
|
||||||
/* pass a queue for replies */
|
/* pass Context */
|
||||||
let mut queue : VecDeque<UIEvent> = VecDeque::new();
|
|
||||||
/* inform each entity */ for i in 0..self.entities.len() {
|
/* inform each entity */ for i in 0..self.entities.len() {
|
||||||
self.entities[i].rcv_event(&event, &mut queue);
|
self.entities[i].rcv_event(&event, &mut self.context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,12 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
|
||||||
(p.0, new_y)
|
(p.0, new_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Area = (Pos, Pos);
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! upper_left { ($a:expr) => ( $a.0 ) }
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) }
|
||||||
|
|
||||||
/// A `(cols, rows)` size.
|
/// A `(cols, rows)` size.
|
||||||
pub type Size = (usize, usize);
|
pub type Size = (usize, usize);
|
||||||
|
|
Loading…
Reference in New Issue