ui: remove Entity

embed
Manos Pitsidianakis 2019-04-10 22:01:02 +03:00
parent b993375fa0
commit 106744c7ca
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
22 changed files with 343 additions and 180 deletions

View File

@ -62,32 +62,30 @@ fn main() {
let worker_receiver = state.worker_receiver(); let worker_receiver = state.worker_receiver();
/* Register some reasonably useful interfaces */ /* Register some reasonably useful interfaces */
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts))); let menu = Box::new(AccountMenu::new(&state.context.accounts));
let listing = listing::Listing::from(IndexStyle::Compact); let listing = listing::Listing::from(IndexStyle::Compact);
let b = Entity::from(Box::new(listing)); let b = Box::new(listing);
let tabs = Box::new(Tabbed::new(vec![ let window = Box::new(Tabbed::new(vec![
Box::new(VSplit::new(menu, b, 90, false)), Box::new(VSplit::new(menu, b, 90, false)),
Box::new(AccountsPanel::new(&state.context)), Box::new(AccountsPanel::new(&state.context)),
Box::new(ContactList::default()), Box::new(ContactList::default()),
])); ]));
let window = Entity::from(tabs);
let status_bar = Entity::from(Box::new(StatusBar::new(window))); let status_bar = Box::new(StatusBar::new(window));
state.register_entity(status_bar); state.register_component(status_bar);
let xdg_notifications = let xdg_notifications = Box::new(ui::components::notifications::XDGNotifications {});
Entity::from(Box::new(ui::components::notifications::XDGNotifications {})); state.register_component(xdg_notifications);
state.register_entity(xdg_notifications); state.register_component(Box::new(
state.register_entity(Entity::from(Box::new(
ui::components::notifications::NotificationFilter {}, ui::components::notifications::NotificationFilter {},
))); ));
/* Keep track of the input mode. See ui::UIMode for details */ /* Keep track of the input mode. See ui::UIMode for details */
'main: loop { 'main: loop {
state.render(); state.render();
'inner: loop { 'inner: loop {
/* Check if any entities have sent reply events to State. */ /* Check if any components have sent reply events to State. */
let events: Vec<UIEvent> = state.context.replies(); let events: Vec<UIEvent> = state.context.replies();
for e in events { for e in events {
state.rcv_event(e); state.rcv_event(e);

View File

@ -76,69 +76,7 @@ const _DOUBLE_DOWN_AND_LEFT: char = '╗';
const _DOUBLE_UP_AND_LEFT: char = '╝'; const _DOUBLE_UP_AND_LEFT: char = '╝';
const _DOUBLE_UP_AND_RIGHT: char = '╚'; const _DOUBLE_UP_AND_RIGHT: char = '╚';
type EntityId = Uuid; type ComponentId = Uuid;
/// `Entity` is a container for Components.
#[derive(Debug)]
pub struct Entity {
id: EntityId,
pub component: Box<Component>, // more than one?
}
impl From<Box<Component>> for Entity {
fn from(mut kind: Box<Component>) -> Entity {
let id = Uuid::new_v4();
kind.set_id(id);
Entity {
id,
component: kind,
}
}
}
impl<C: 'static> From<Box<C>> for Entity
where
C: Component,
{
fn from(mut kind: Box<C>) -> Entity {
let id = Uuid::new_v4();
kind.set_id(id);
Entity {
id,
component: kind,
}
}
}
impl Display for Entity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.component, f)
}
}
impl DerefMut for Entity {
fn deref_mut(&mut self) -> &mut Box<Component> {
&mut self.component
}
}
impl Deref for Entity {
type Target = Box<Component>;
fn deref(&self) -> &Box<Component> {
&self.component
}
}
impl Entity {
pub fn id(&self) -> &EntityId {
&self.id
}
/// Pass events to child component.
pub fn rcv_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
self.component.process_event(event, context)
}
}
pub type ShortcutMap = FnvHashMap<&'static str, Key>; pub type ShortcutMap = FnvHashMap<&'static str, Key>;
@ -155,8 +93,9 @@ pub trait Component: Display + Debug + Send {
true true
} }
fn set_dirty(&mut self); fn set_dirty(&mut self);
fn kill(&mut self, _id: EntityId) {} fn kill(&mut self, _id: ComponentId) {}
fn set_id(&mut self, _id: EntityId) {} fn set_id(&mut self, _id: ComponentId) {}
fn id(&self) -> ComponentId;
fn get_shortcuts(&self, _context: &Context) -> ShortcutMap { fn get_shortcuts(&self, _context: &Context) -> ShortcutMap {
Default::default() Default::default()

View File

@ -36,7 +36,7 @@ enum ViewMode {
#[derive(Debug)] #[derive(Debug)]
pub struct ContactManager { pub struct ContactManager {
id: Uuid, id: ComponentId,
pub card: Card, pub card: Card,
mode: ViewMode, mode: ViewMode,
form: FormWidget, form: FormWidget,
@ -169,13 +169,13 @@ impl Component for ContactManager {
}); });
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
event_type: UIEventType::EntityKill(self.id), event_type: UIEventType::ComponentKill(self.id),
}); });
} }
Some(false) => { Some(false) => {
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
event_type: UIEventType::EntityKill(self.id), event_type: UIEventType::ComponentKill(self.id),
}); });
} }
} }
@ -186,7 +186,7 @@ impl Component for ContactManager {
UIEventType::Input(Key::Char('\n')) => { UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
event_type: UIEventType::EntityKill(self.id), event_type: UIEventType::ComponentKill(self.id),
}); });
return true; return true;
}, },
@ -206,7 +206,10 @@ impl Component for ContactManager {
self.form.set_dirty(); self.form.set_dirty();
} }
fn set_id(&mut self, uuid: Uuid) { fn id(&self) -> ComponentId {
self.id = uuid; self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
} }
} }

View File

@ -23,7 +23,8 @@ pub struct ContactList {
mode: ViewMode, mode: ViewMode,
dirty: bool, dirty: bool,
view: Option<Entity>, view: Option<Box<Component>>,
id: ComponentId,
} }
impl Default for ContactList { impl Default for ContactList {
@ -51,6 +52,7 @@ impl ContactList {
content, content,
dirty: true, dirty: true,
view: None, view: None,
id: ComponentId::default(),
} }
} }
@ -234,10 +236,10 @@ impl Component for ContactList {
UIEventType::Input(ref key) if *key == shortcuts["create_contact"] => { UIEventType::Input(ref key) if *key == shortcuts["create_contact"] => {
let mut manager = ContactManager::default(); let mut manager = ContactManager::default();
manager.account_pos = self.account_pos; manager.account_pos = self.account_pos;
let entity = Entity::from(Box::new(manager)); let component = Box::new(manager);
self.mode = ViewMode::View(*entity.id()); self.mode = ViewMode::View(component.id());
self.view = Some(entity); self.view = Some(component);
return true; return true;
} }
@ -249,10 +251,10 @@ impl Component for ContactList {
let mut manager = ContactManager::default(); let mut manager = ContactManager::default();
manager.card = card; manager.card = card;
manager.account_pos = self.account_pos; manager.account_pos = self.account_pos;
let entity = Entity::from(Box::new(manager)); let component = Box::new(manager);
self.mode = ViewMode::View(*entity.id()); self.mode = ViewMode::View(component.id());
self.view = Some(entity); self.view = Some(component);
return true; return true;
} }
@ -261,9 +263,9 @@ impl Component for ContactList {
let mut manager = ContactManager::default(); let mut manager = ContactManager::default();
manager.card = card; manager.card = card;
manager.account_pos = self.account_pos; manager.account_pos = self.account_pos;
let entity = Entity::from(Box::new(manager)); let component = Box::new(manager);
self.mode = ViewMode::View(*entity.id()); self.mode = ViewMode::View(component.id());
self.view = Some(entity); self.view = Some(component);
return true; return true;
} }
@ -277,7 +279,7 @@ impl Component for ContactList {
self.new_cursor_pos += 1; self.new_cursor_pos += 1;
return true; return true;
} }
UIEventType::EntityKill(ref kill_id) if self.mode == ViewMode::View(*kill_id) => { UIEventType::ComponentKill(ref kill_id) if self.mode == ViewMode::View(*kill_id) => {
self.mode = ViewMode::List; self.mode = ViewMode::List;
self.view.take(); self.view.take();
self.set_dirty(); self.set_dirty();
@ -315,4 +317,11 @@ impl Component for ContactList {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -38,6 +38,7 @@ pub struct Indexer {
entries: Vec<MenuEntry>, entries: Vec<MenuEntry>,
dirty: bool, dirty: bool,
cursor: Vec<usize>, cursor: Vec<usize>,
id: ComponentId,
} }
impl fmt::Display for Indexer { impl fmt::Display for Indexer {
@ -53,6 +54,7 @@ impl Default for Indexer {
entries: Vec::with_capacity(8), entries: Vec::with_capacity(8),
dirty: true, dirty: true,
cursor: Vec::with_capacity(8), cursor: Vec::with_capacity(8),
id: ComponentId::default(),
} }
} }
} }
@ -125,4 +127,11 @@ impl Component for Indexer {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -34,6 +34,7 @@ pub struct Index {
state: IndexState, state: IndexState,
content: Box<IndexContent>, content: Box<IndexContent>,
id: ComponentId,
} }
impl Index { impl Index {
@ -169,6 +170,13 @@ impl Component for Index {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
impl fmt::Display for Index { impl fmt::Display for Index {

View File

@ -48,6 +48,7 @@ pub struct AccountMenu {
dirty: bool, dirty: bool,
visible: bool, visible: bool,
cursor: Option<(usize, usize)>, cursor: Option<(usize, usize)>,
id: ComponentId,
} }
impl fmt::Display for AccountMenu { impl fmt::Display for AccountMenu {
@ -72,6 +73,7 @@ impl AccountMenu {
visible: true, visible: true,
dirty: true, dirty: true,
cursor: None, cursor: None,
id: ComponentId::default(),
} }
} }
/* /*
@ -301,4 +303,11 @@ impl Component for AccountMenu {
.cloned() .cloned()
.collect() .collect()
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -27,6 +27,7 @@ pub struct AccountsPanel {
cursor: usize, cursor: usize,
content: CellBuffer, content: CellBuffer,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl fmt::Display for AccountsPanel { impl fmt::Display for AccountsPanel {
@ -84,6 +85,13 @@ impl Component for AccountsPanel {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
impl AccountsPanel { impl AccountsPanel {
@ -94,6 +102,7 @@ impl AccountsPanel {
cursor: 0, cursor: 0,
content, content,
dirty: true, dirty: true,
id: ComponentId::default(),
} }
} }
fn initialize(&mut self, context: &Context) { fn initialize(&mut self, context: &Context) {

View File

@ -45,6 +45,7 @@ pub struct Composer {
mode: ViewMode, mode: ViewMode,
dirty: bool, dirty: bool,
initialized: bool, initialized: bool,
id: ComponentId,
} }
impl Default for Composer { impl Default for Composer {
@ -62,6 +63,7 @@ impl Default for Composer {
mode: ViewMode::Edit, mode: ViewMode::Edit,
dirty: true, dirty: true,
initialized: false, initialized: false,
id: ComponentId::default(),
} }
} }
} }
@ -688,6 +690,13 @@ impl Component for Composer {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
fn get_display_name(context: &Context, idx: usize) -> String { fn get_display_name(context: &Context, idx: usize) -> String {

View File

@ -163,6 +163,21 @@ impl Component for Listing {
Listing::Threaded(l) => l.get_shortcuts(context), Listing::Threaded(l) => l.get_shortcuts(context),
} }
} }
fn id(&self) -> ComponentId {
match self {
Listing::Compact(l) => l.id(),
Listing::Plain(l) => l.id(),
Listing::Threaded(l) => l.id(),
}
}
fn set_id(&mut self, id: ComponentId) {
match self {
Listing::Compact(l) => l.set_id(id),
Listing::Plain(l) => l.set_id(id),
Listing::Threaded(l) => l.set_id(id),
}
}
} }
impl From<IndexStyle> for Listing { impl From<IndexStyle> for Listing {

View File

@ -43,6 +43,7 @@ struct MailboxView {
view: ThreadView, view: ThreadView,
movement: Option<PageMovement>, movement: Option<PageMovement>,
id: ComponentId,
} }
impl fmt::Display for MailboxView { impl fmt::Display for MailboxView {
@ -87,6 +88,7 @@ impl MailboxView {
view: ThreadView::default(), view: ThreadView::default(),
movement: None, movement: None,
id: ComponentId::default(),
} }
} }
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
@ -587,6 +589,13 @@ impl Component for MailboxView {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
/// 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
@ -597,6 +606,7 @@ pub struct CompactListing {
cursor: usize, cursor: usize,
dirty: bool, dirty: bool,
populated: bool, populated: bool,
id: ComponentId,
} }
impl ListingTrait for CompactListing { impl ListingTrait for CompactListing {
@ -627,6 +637,7 @@ impl CompactListing {
cursor: 0, cursor: 0,
dirty: true, dirty: true,
populated: false, populated: false,
id: ComponentId::default(),
} }
} }
} }
@ -762,4 +773,11 @@ impl Component for CompactListing {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -41,6 +41,7 @@ pub struct PlainListing {
/// If `self.view` exists or not. /// If `self.view` exists or not.
unfocused: bool, unfocused: bool,
view: Option<MailView>, view: Option<MailView>,
id: ComponentId,
} }
impl ListingTrait for PlainListing { impl ListingTrait for PlainListing {
@ -89,6 +90,7 @@ impl PlainListing {
dirty: true, dirty: true,
unfocused: false, unfocused: false,
view: None, view: None,
id: ComponentId::default(),
} }
} }
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
@ -561,4 +563,11 @@ impl Component for PlainListing {
}; };
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -44,6 +44,7 @@ pub struct ThreadListing {
unfocused: bool, unfocused: bool,
initialised: bool, initialised: bool,
view: Option<MailView>, view: Option<MailView>,
id: ComponentId,
} }
impl ListingTrait for ThreadListing { impl ListingTrait for ThreadListing {
@ -86,6 +87,7 @@ impl ThreadListing {
unfocused: false, unfocused: false,
view: None, view: None,
initialised: false, initialised: false,
id: ComponentId::default(),
} }
} }
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
@ -729,4 +731,11 @@ impl Component for ThreadListing {
.map(|p| p.get_shortcuts(context)) .map(|p| p.get_shortcuts(context))
.unwrap_or_default() .unwrap_or_default()
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -69,6 +69,7 @@ pub struct MailView {
mode: ViewMode, mode: ViewMode,
cmd_buf: String, cmd_buf: String,
id: ComponentId,
} }
impl fmt::Display for MailView { impl fmt::Display for MailView {
@ -112,6 +113,7 @@ impl MailView {
mode: ViewMode::Normal, mode: ViewMode::Normal,
cmd_buf: String::with_capacity(4), cmd_buf: String::with_capacity(4),
id: ComponentId::default(),
} }
} }
@ -723,4 +725,11 @@ impl Component for MailView {
_ => {} _ => {}
} }
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -55,6 +55,7 @@ pub struct EnvelopeView {
account_pos: usize, account_pos: usize,
cmd_buf: String, cmd_buf: String,
id: ComponentId,
} }
impl fmt::Display for EnvelopeView { impl fmt::Display for EnvelopeView {
@ -79,6 +80,7 @@ impl EnvelopeView {
wrapper, wrapper,
account_pos, account_pos,
cmd_buf: String::with_capacity(4), cmd_buf: String::with_capacity(4),
id: ComponentId::default(),
} }
} }
@ -545,4 +547,11 @@ impl Component for EnvelopeView {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -27,10 +27,12 @@ use std::process::{Command, Stdio};
pub struct HtmlView { pub struct HtmlView {
pager: Pager, pager: Pager,
bytes: Vec<u8>, bytes: Vec<u8>,
id: ComponentId,
} }
impl HtmlView { impl HtmlView {
pub fn new(bytes: Vec<u8>, context: &mut Context, account_pos: usize) -> Self { pub fn new(bytes: Vec<u8>, context: &mut Context, account_pos: usize) -> Self {
let id = ComponentId::default();
let settings = context.accounts[account_pos].runtime_settings.conf(); let settings = context.accounts[account_pos].runtime_settings.conf();
if let Some(filter_invocation) = settings.html_filter() { if let Some(filter_invocation) = settings.html_filter() {
let parts = split_command!(filter_invocation); let parts = split_command!(filter_invocation);
@ -57,7 +59,7 @@ impl HtmlView {
None, None,
None, None,
); );
HtmlView { pager, bytes } HtmlView { pager, bytes, id }
} else { } else {
let mut html_filter = command_obj.unwrap(); let mut html_filter = command_obj.unwrap();
html_filter html_filter
@ -75,7 +77,7 @@ impl HtmlView {
)); ));
let pager = Pager::from_string(display_text, None, None, None); let pager = Pager::from_string(display_text, None, None, None);
HtmlView { pager, bytes } HtmlView { pager, bytes, id }
} }
} else { } else {
if let Ok(mut html_filter) = Command::new("w3m") if let Ok(mut html_filter) = Command::new("w3m")
@ -98,7 +100,7 @@ impl HtmlView {
)); ));
let pager = Pager::from_string(display_text, None, None, None); let pager = Pager::from_string(display_text, None, None, None);
HtmlView { pager, bytes } HtmlView { pager, bytes, id }
} else { } else {
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
@ -115,7 +117,7 @@ impl HtmlView {
None, None,
None, None,
); );
HtmlView { pager, bytes } HtmlView { pager, bytes, id }
} }
} }
} }
@ -168,4 +170,11 @@ impl Component for HtmlView {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.pager.set_dirty(); self.pager.set_dirty();
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -51,6 +51,7 @@ pub struct ThreadView {
dirty: bool, dirty: bool,
content: CellBuffer, content: CellBuffer,
initiated: bool, initiated: bool,
id: ComponentId,
} }
#[derive(Debug)] #[derive(Debug)]
@ -985,4 +986,11 @@ impl Component for ThreadView {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }

View File

@ -54,6 +54,11 @@ impl Component for XDGNotifications {
false false
} }
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
fn id(&self) -> ComponentId {
ComponentId::nil()
}
fn set_id(&mut self, _id: ComponentId) {}
} }
fn escape_str(s: &str) -> String { fn escape_str(s: &str) -> String {
@ -133,5 +138,9 @@ impl Component for NotificationFilter {
} }
false false
} }
fn id(&self) -> ComponentId {
ComponentId::nil()
}
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
fn set_id(&mut self, _id: ComponentId) {}
} }

View File

@ -30,10 +30,11 @@ pub use self::widgets::*;
/// A horizontally split in half container. /// A horizontally split in half container.
#[derive(Debug)] #[derive(Debug)]
pub struct HSplit { pub struct HSplit {
top: Entity, top: Box<Component>,
bottom: Entity, bottom: Box<Component>,
show_divider: bool, show_divider: bool,
ratio: usize, // bottom/whole height * 100 ratio: usize, // bottom/whole height * 100
id: ComponentId,
} }
impl fmt::Display for HSplit { impl fmt::Display for HSplit {
@ -44,12 +45,18 @@ impl fmt::Display for HSplit {
} }
impl HSplit { impl HSplit {
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self { pub fn new(
top: Box<Component>,
bottom: Box<Component>,
ratio: usize,
show_divider: bool,
) -> Self {
HSplit { HSplit {
top, top,
bottom, bottom,
show_divider, show_divider,
ratio, ratio,
id: ComponentId::default(),
} }
} }
} }
@ -62,8 +69,8 @@ impl Component for HSplit {
let upper_left = upper_left!(area); let upper_left = upper_left!(area);
let bottom_right = bottom_right!(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_component_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_component_height;
if self.show_divider { if self.show_divider {
for i in get_x(upper_left)..=get_x(bottom_right) { for i in get_x(upper_left)..=get_x(bottom_right) {
@ -74,7 +81,7 @@ impl Component for HSplit {
.push_back(((get_x(upper_left), mid), (get_x(bottom_right), mid))); .push_back(((get_x(upper_left), mid), (get_x(bottom_right), mid)));
} }
self.top.component.draw( self.top.draw(
grid, grid,
( (
upper_left, upper_left,
@ -82,23 +89,23 @@ impl Component for HSplit {
), ),
context, context,
); );
self.bottom.component.draw( self.bottom.draw(
grid, grid,
((get_x(upper_left), get_y(upper_left) + mid), bottom_right), ((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
context, context,
); );
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
self.top.rcv_event(event, context) || self.bottom.rcv_event(event, context) self.top.process_event(event, context) || self.bottom.process_event(event, context)
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.top.component.is_dirty() || self.bottom.component.is_dirty() self.top.is_dirty() || self.bottom.is_dirty()
} }
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.top.component.set_dirty(); self.top.set_dirty();
self.bottom.component.set_dirty(); self.bottom.set_dirty();
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMap { fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
@ -106,34 +113,48 @@ impl Component for HSplit {
top_map.extend(self.bottom.get_shortcuts(context).into_iter()); top_map.extend(self.bottom.get_shortcuts(context).into_iter());
top_map top_map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
/// A vertically split in half container. /// A vertically split in half container.
#[derive(Debug)] #[derive(Debug)]
pub struct VSplit { pub struct VSplit {
left: Entity, left: Box<Component>,
right: Entity, right: Box<Component>,
show_divider: bool, show_divider: bool,
prev_visibility: (bool, bool), prev_visibility: (bool, bool),
/// This is the width of the right container to the entire width. /// This is the width of the right container to the entire width.
ratio: usize, // right/(container width) * 100 ratio: usize, // right/(container width) * 100
id: ComponentId,
} }
impl fmt::Display for VSplit { impl fmt::Display for VSplit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display focused entity // TODO display focused component
Display::fmt(&self.right, f) Display::fmt(&self.right, f)
} }
} }
impl VSplit { impl VSplit {
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self { pub fn new(
left: Box<Component>,
right: Box<Component>,
ratio: usize,
show_divider: bool,
) -> Self {
VSplit { VSplit {
left, left,
right, right,
show_divider, show_divider,
prev_visibility: (true, true), prev_visibility: (true, true),
ratio, ratio,
id: ComponentId::default(),
} }
} }
} }
@ -151,7 +172,7 @@ impl Component for VSplit {
self.set_dirty(); self.set_dirty();
self.prev_visibility = visibility; self.prev_visibility = visibility;
} }
let right_entity_width = match visibility { let right_component_width = match visibility {
(true, true) => (self.ratio * total_cols) / 100, (true, true) => (self.ratio * total_cols) / 100,
(false, true) => total_cols, (false, true) => total_cols,
(true, false) => 0, (true, false) => 0,
@ -161,7 +182,7 @@ impl Component for VSplit {
} }
}; };
let mid = get_x(bottom_right) - right_entity_width; let mid = get_x(bottom_right) - right_component_width;
if get_y(upper_left) > 1 { if get_y(upper_left) > 1 {
let c = grid let c = grid
@ -193,12 +214,12 @@ impl Component for VSplit {
.push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right)))); .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right))));
} }
if right_entity_width == total_cols { if right_component_width == total_cols {
self.right.component.draw(grid, area, context); self.right.draw(grid, area, context);
} else if right_entity_width == 0 { } else if right_component_width == 0 {
self.left.component.draw(grid, area, context); self.left.draw(grid, area, context);
} else { } else {
self.left.component.draw( self.left.draw(
grid, grid,
( (
upper_left, upper_left,
@ -210,22 +231,21 @@ impl Component for VSplit {
context, context,
); );
self.right self.right
.component
.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context); .draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
} }
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
(self.left.rcv_event(event, context) || self.right.rcv_event(event, context)) (self.left.process_event(event, context) || self.right.process_event(event, context))
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.left.component.is_dirty() || self.right.component.is_dirty() self.left.is_dirty() || self.right.is_dirty()
} }
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.left.component.set_dirty(); self.left.set_dirty();
self.right.component.set_dirty(); self.right.set_dirty();
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMap { fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
@ -233,6 +253,13 @@ impl Component for VSplit {
right_map.extend(self.left.get_shortcuts(context).into_iter()); right_map.extend(self.left.get_shortcuts(context).into_iter());
right_map right_map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -254,6 +281,7 @@ pub struct Pager {
dirty: bool, dirty: bool,
content: CellBuffer, content: CellBuffer,
movement: Option<PageMovement>, movement: Option<PageMovement>,
id: ComponentId,
} }
impl fmt::Display for Pager { impl fmt::Display for Pager {
@ -524,12 +552,19 @@ impl Component for Pager {
map map
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
/// Status bar. /// Status bar.
#[derive(Debug)] #[derive(Debug)]
pub struct StatusBar { pub struct StatusBar {
container: Entity, container: Box<Component>,
status: String, status: String,
notifications: VecDeque<String>, notifications: VecDeque<String>,
ex_buffer: String, ex_buffer: String,
@ -537,6 +572,7 @@ pub struct StatusBar {
mode: UIMode, mode: UIMode,
height: usize, height: usize,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl fmt::Display for StatusBar { impl fmt::Display for StatusBar {
@ -547,7 +583,7 @@ impl fmt::Display for StatusBar {
} }
impl StatusBar { impl StatusBar {
pub fn new(container: Entity) -> Self { pub fn new(container: Box<Component>) -> Self {
StatusBar { StatusBar {
container, container,
status: String::with_capacity(256), status: String::with_capacity(256),
@ -557,6 +593,7 @@ impl StatusBar {
dirty: true, dirty: true,
mode: UIMode::Normal, mode: UIMode::Normal,
height: 1, height: 1,
id: ComponentId::default(),
} }
} }
fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
@ -615,7 +652,7 @@ impl Component for StatusBar {
} }
let height = self.height; let height = self.height;
self.container.component.draw( self.container.draw(
grid, grid,
( (
upper_left, upper_left,
@ -649,7 +686,7 @@ impl Component for StatusBar {
} }
} }
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.container.rcv_event(event, context) { if self.container.process_event(event, context) {
return true; return true;
} }
@ -728,7 +765,7 @@ impl Component for StatusBar {
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty || self.container.component.is_dirty() self.dirty || self.container.is_dirty()
} }
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
@ -737,40 +774,21 @@ impl Component for StatusBar {
fn get_shortcuts(&self, context: &Context) -> ShortcutMap { fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
self.container.get_shortcuts(context) self.container.get_shortcuts(context)
} }
}
// A box with a text content. fn id(&self) -> ComponentId {
#[derive(Debug)] self.id
pub struct TextBox {
_content: String,
}
impl TextBox {
pub fn new(s: String) -> Self {
TextBox { _content: s }
} }
} fn set_id(&mut self, id: ComponentId) {
self.id = id;
impl fmt::Display for TextBox {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display info
write!(f, "text box")
} }
} }
impl Component for TextBox {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
false
}
fn set_dirty(&mut self) {}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Progress { pub struct Progress {
description: String, description: String,
total_work: usize, total_work: usize,
finished: usize, finished: usize,
id: ComponentId,
} }
impl Progress { impl Progress {
@ -779,6 +797,7 @@ impl Progress {
description: s, description: s,
total_work, total_work,
finished: 0, finished: 0,
id: ComponentId::default(),
} }
} }
@ -817,17 +836,25 @@ impl Component for Progress {
false false
} }
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Tabbed { pub struct Tabbed {
pinned: usize, pinned: usize,
children: Vec<Entity>, children: Vec<Box<Component>>,
cursor_pos: usize, cursor_pos: usize,
show_shortcuts: bool, show_shortcuts: bool,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl Tabbed { impl Tabbed {
@ -835,10 +862,11 @@ impl Tabbed {
let pinned = children.len(); let pinned = children.len();
Tabbed { Tabbed {
pinned, pinned,
children: children.into_iter().map(Entity::from).collect(), children,
cursor_pos: 0, cursor_pos: 0,
show_shortcuts: false, show_shortcuts: false,
dirty: true, dirty: true,
id: ComponentId::default(),
} }
} }
fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
@ -895,7 +923,7 @@ impl Tabbed {
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
pub fn add_component(&mut self, new: Box<Component>) { pub fn add_component(&mut self, new: Box<Component>) {
self.children.push(Entity::from(new)); self.children.push(new);
} }
} }
@ -1027,7 +1055,7 @@ impl Component for Tabbed {
if self.pinned > self.cursor_pos { if self.pinned > self.cursor_pos {
return true; return true;
} }
let id = *self.children[self.cursor_pos].id(); let id = self.children[self.cursor_pos].id();
self.children[self.cursor_pos].kill(id); self.children[self.cursor_pos].kill(id);
self.set_dirty(); self.set_dirty();
return true; return true;
@ -1036,14 +1064,14 @@ impl Component for Tabbed {
if self.pinned > self.cursor_pos { if self.pinned > self.cursor_pos {
return true; return true;
} }
if let Some(c_idx) = self.children.iter().position(|x| x.id() == id) { if let Some(c_idx) = self.children.iter().position(|x| x.id() == *id) {
self.children.remove(c_idx); self.children.remove(c_idx);
self.cursor_pos = self.cursor_pos.saturating_sub(1); self.cursor_pos = self.cursor_pos.saturating_sub(1);
self.set_dirty(); self.set_dirty();
return true; return true;
} else { } else {
eprintln!( eprintln!(
"DEBUG: Child entity with id {:?} not found.\nList: {:?}", "DEBUG: Child component with id {:?} not found.\nList: {:?}",
id, self.children id, self.children
); );
} }
@ -1059,6 +1087,13 @@ impl Component for Tabbed {
self.dirty = true; self.dirty = true;
self.children[self.cursor_pos].set_dirty(); self.children[self.cursor_pos].set_dirty();
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
type EntryIdentifier = Vec<u8>; type EntryIdentifier = Vec<u8>;
@ -1074,6 +1109,7 @@ pub struct Selector {
cursor: usize, cursor: usize,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl fmt::Display for Selector { impl fmt::Display for Selector {
@ -1136,6 +1172,13 @@ impl Component for Selector {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
impl Selector { impl Selector {
@ -1170,6 +1213,7 @@ impl Selector {
content, content,
cursor: 0, cursor: 0,
dirty: true, dirty: true,
id: ComponentId::default(),
} }
} }

View File

@ -187,6 +187,11 @@ impl Component for Field {
true true
} }
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
fn id(&self) -> ComponentId {
ComponentId::nil()
}
fn set_id(&mut self, _id: ComponentId) {}
} }
impl fmt::Display for Field { impl fmt::Display for Field {
@ -206,6 +211,7 @@ pub struct FormWidget {
focus: FormFocus, focus: FormFocus,
hide_buttons: bool, hide_buttons: bool,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl fmt::Display for FormWidget { impl fmt::Display for FormWidget {
@ -441,6 +447,13 @@ impl Component for FormWidget {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -453,6 +466,7 @@ where
result: Option<T>, result: Option<T>,
cursor: usize, cursor: usize,
id: ComponentId,
} }
impl<T> fmt::Display for ButtonWidget<T> impl<T> fmt::Display for ButtonWidget<T>
@ -474,6 +488,7 @@ where
buttons: vec![init_val].into_iter().collect(), buttons: vec![init_val].into_iter().collect(),
result: None, result: None,
cursor: 0, cursor: 0,
id: ComponentId::default(),
} }
} }
@ -542,6 +557,13 @@ where
true true
} }
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -551,6 +573,7 @@ pub struct AutoComplete {
cursor: usize, cursor: usize,
dirty: bool, dirty: bool,
id: ComponentId,
} }
impl fmt::Display for AutoComplete { impl fmt::Display for AutoComplete {
@ -595,6 +618,13 @@ impl Component for AutoComplete {
fn set_dirty(&mut self) { fn set_dirty(&mut self) {
self.dirty = true; self.dirty = true;
} }
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
} }
impl AutoComplete { impl AutoComplete {
@ -604,6 +634,7 @@ impl AutoComplete {
content: CellBuffer::default(), content: CellBuffer::default(),
cursor: 0, cursor: 0,
dirty: true, dirty: true,
id: ComponentId::default(),
}; };
ret.set_suggestions(entries); ret.set_suggestions(entries);
ret ret

View File

@ -21,9 +21,9 @@
/*! The application's state. /*! The application's state.
The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct. The UI crate has an Box<Component>-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
`State` owns all the Entities of the UI, which are currently plain Containers for `Component`s. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the entity graph. Components decide to handle each input or not. `State` owns all the Components of the UI. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the component graph. Components decide to handle each input or not.
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`. Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
*/ */
@ -115,7 +115,7 @@ impl Context {
} }
} }
/// A State object to manage and own components and entities of the UI. `State` is responsible for /// A State object to manage and own components and components of the UI. `State` is responsible for
/// managing the terminal and interfacing with `melib` /// managing the terminal and interfacing with `melib`
pub struct State { pub struct State {
cols: usize, cols: usize,
@ -125,7 +125,7 @@ pub struct State {
stdout: Option<StateStdout>, stdout: Option<StateStdout>,
child: Option<ForkType>, child: Option<ForkType>,
pub mode: UIMode, pub mode: UIMode,
entities: Vec<Entity>, components: Vec<Box<Component>>,
pub context: Context, pub context: Context,
threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>, threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>,
work_controller: WorkController, work_controller: WorkController,
@ -199,7 +199,7 @@ impl State {
stdout: Some(stdout), stdout: Some(stdout),
child: None, child: None,
mode: UIMode::Normal, mode: UIMode::Normal,
entities: Vec::with_capacity(1), components: Vec::with_capacity(1),
context: Context { context: Context {
accounts, accounts,
@ -376,8 +376,8 @@ impl State {
/// Force a redraw for all dirty components. /// Force a redraw for all dirty components.
pub fn redraw(&mut self) { pub fn redraw(&mut self) {
for i in 0..self.entities.len() { for i in 0..self.components.len() {
self.draw_entity(i); self.draw_component(i);
} }
let areas: Vec<Area> = self.context.dirty_areas.drain(0..).collect(); let areas: Vec<Area> = self.context.dirty_areas.drain(0..).collect();
/* draw each dirty area */ /* draw each dirty area */
@ -433,30 +433,30 @@ impl State {
pub fn render(&mut self) { pub fn render(&mut self) {
self.update_size(); self.update_size();
/* draw each entity */ /* draw each component */
for i in 0..self.entities.len() { for i in 0..self.components.len() {
self.draw_entity(i); self.draw_component(i);
} }
let cols = self.cols; let cols = self.cols;
let rows = self.rows; let rows = self.rows;
self.draw_area(((0, 0), (cols - 1, rows - 1))); self.draw_area(((0, 0), (cols - 1, rows - 1)));
} }
pub fn draw_entity(&mut self, idx: usize) { pub fn draw_component(&mut self, idx: usize) {
let entity = &mut self.entities[idx]; let component = &mut self.components[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);
if entity.component.is_dirty() { if component.is_dirty() {
entity.component.draw( component.draw(
&mut self.grid, &mut self.grid,
(upper_left, bottom_right), (upper_left, bottom_right),
&mut self.context, &mut self.context,
); );
} }
} }
pub fn register_entity(&mut self, entity: Entity) { pub fn register_component(&mut self, component: Box<Component>) {
self.entities.push(entity); self.components.push(component);
} }
/// Convert user commands to actions/method calls. /// Convert user commands to actions/method calls.
fn parse_command(&mut self, cmd: &str) { fn parse_command(&mut self, cmd: &str) {
@ -499,9 +499,9 @@ impl State {
} }
_ => {} _ => {}
} }
/* inform each entity */ /* inform each component */
for i in 0..self.entities.len() { for i in 0..self.components.len() {
self.entities[i].rcv_event(&mut event, &mut self.context); self.components[i].process_event(&mut event, &mut self.context);
} }
if !self.context.replies.is_empty() { if !self.context.replies.is_empty() {

View File

@ -84,7 +84,7 @@ pub enum UIEventType {
Action(Action), Action(Action),
StatusEvent(StatusEvent), StatusEvent(StatusEvent),
MailboxUpdate((usize, usize)), // (account_idx, mailbox_idx) MailboxUpdate((usize, usize)), // (account_idx, mailbox_idx)
EntityKill(Uuid), ComponentKill(Uuid),
StartupCheck(FolderHash), StartupCheck(FolderHash),
RefreshEvent(Box<RefreshEvent>), RefreshEvent(Box<RefreshEvent>),
EnvelopeUpdate(EnvelopeHash), EnvelopeUpdate(EnvelopeHash),