exoplanet/src/tui.rs

449 lines
14 KiB
Rust

use crate::pronouns::Pronouns;
use crate::protocol::Message;
use crate::state::State;
use crossbeam_channel::Sender;
use cursive::align::*;
use cursive::event::{Event, Key};
use cursive::theme::*;
use cursive::traits::*;
use cursive::view::*;
use cursive::views::*;
use cursive::Cursive;
#[derive(Clone, Default)]
pub struct LoginForm {
pub username: Option<String>,
pub about: Option<String>,
pub port: Option<String>,
pub pronouns: Option<Pronouns>,
}
pub fn make_cursive() -> Cursive {
let mut cursive = Cursive::new();
cursive.set_user_data(LoginForm::default());
cursive.update_theme(|theme| {
theme.shadow = false;
theme.borders = BorderStyle::Simple;
let palette = &mut theme.palette;
palette[PaletteColor::Background] = Color::TerminalDefault;
palette[PaletteColor::View] = Color::TerminalDefault;
palette[PaletteColor::Primary] = Color::TerminalDefault;
});
cursive.add_global_callback(Event::Char('h'), |siv| siv.on_event(Event::Key(Key::Left)));
cursive.add_global_callback(Event::Char('j'), |siv| siv.on_event(Event::Key(Key::Down)));
cursive.add_global_callback(Event::Char('k'), |siv| siv.on_event(Event::Key(Key::Up)));
cursive.add_global_callback(Event::Char('l'), |siv| siv.on_event(Event::Key(Key::Right)));
show_welcome_mat(&mut cursive);
cursive
}
pub fn add_message(siv: &mut Cursive, message: &Message) {
siv.call_on_name("messages_list", |messages: &mut LinearLayout| {
let text = format!("{:<16}{}", message.sender, message.contents);
let mut text = TextView::new(text);
text.set_content_wrap(true);
messages.add_child(text);
});
siv.call_on_name(
"messages_list_scroll",
|scroll: &mut ScrollView<NamedView<LinearLayout>>| {
if scroll.is_at_bottom() {
scroll.set_scroll_strategy(ScrollStrategy::StickToBottom);
}
},
);
}
pub fn show_welcome_mat(siv: &mut Cursive) {
let logo = TextView::new(include_str!("logo.txt")).center();
let labels = make_vertical_labels(&["Identity:", "Listen port:"]);
let identity = Button::new_raw("<none>", |siv| edit_identity(siv)).with_name("identity_button");
let port = EditView::new().with_name("port_edit");
let values = LinearLayout::vertical().child(identity).child(port);
let config = LinearLayout::horizontal().child(labels).child(values);
let layout = LinearLayout::vertical().child(logo).child(config);
let dialog = Dialog::around(layout)
.title("Welcome User!")
.button("Link start!", |siv| {
let mut form = siv.with_user_data(|f: &mut LoginForm| f.clone()).unwrap();
let port = get_edit_contents(siv, "port_edit");
form.port = Some(port.clone());
let username = match form.username.as_ref() {
Some(val) => val.clone(),
None => {
let dialog = Dialog::info("Please select an identity!");
return siv.add_layer(dialog);
}
};
let about = match form.about.as_ref() {
Some(val) => val.clone(),
None => {
let dialog = Dialog::info("Please select an identity!");
return siv.add_layer(dialog);
}
};
let pronouns = form.pronouns.clone();
let listen_port: u16 = match port.parse() {
Ok(val) => val,
Err(_) => {
let dialog = Dialog::info("Please select a valid port number!");
return siv.add_layer(dialog);
}
};
let info = crate::state::LoginInfo {
identity: crate::state::Identity {
username,
about,
pronouns,
},
listen_port,
};
let state = match crate::state::State::new(info) {
Ok(val) => val,
Err(err) => {
let message = format!("{:?}", err);
let dialog = Dialog::info(message).title("Login Error");
return siv.add_layer(dialog);
}
};
siv.set_user_data(state);
siv.pop_layer();
show_main(siv);
})
.button("Quit", Cursive::quit);
siv.add_layer(dialog);
}
pub fn edit_identity(siv: &mut Cursive) {
let labels = make_vertical_labels(&["Username:", "About:", "Pronouns:"]).fixed_width(10);
let values = LinearLayout::vertical()
.child(EditView::new().with_name("username_edit"))
.child(EditView::new().with_name("about_edit"))
.child(Button::new("none", |siv| select_pronouns(siv)).with_name("pronouns_button"))
.fixed_width(45);
let columns = LinearLayout::horizontal().child(labels).child(values);
let dialog =
Dialog::around(columns)
.title("Edit Identity")
.button("Ok", |siv: &mut Cursive| {
let username = get_edit_contents(siv, "username_edit");
let about = get_edit_contents(siv, "about_edit");
if username.chars().count() < 1 {
let dialog = Dialog::info("Usernames must be at least one character!");
return siv.add_layer(dialog);
}
siv.with_user_data(|form: &mut LoginForm| {
form.username = Some(username);
form.about = Some(about);
});
siv.pop_layer();
});
siv.add_layer(dialog);
}
fn make_example_usage_panel() -> impl View {
let text = TextView::new("Highlight a pronoun set to preview its usage!")
.with_name("pronoun_example_text")
.fixed_width(50)
.scrollable();
Panel::new(text).title("Example Usage")
}
fn update_pronouns_edit(siv: &mut Cursive, pronouns: &Pronouns) {
siv.call_on_name("pronoun_example_text", |view: &mut TextView| {
view.set_content(pronouns.make_example_usage());
});
siv.call_on_name("pronouns_button", |view: &mut Button| {
view.set_label(pronouns.format_full());
})
.unwrap();
siv.with_user_data(|form: &mut LoginForm| {
form.pronouns = Some(pronouns.clone());
});
}
pub fn select_pronouns(siv: &mut Cursive) {
let example = TextView::new("Highlight a pronoun set to preview its usage!")
.with_name("pronoun_example_text")
.fixed_width(35)
.scrollable();
let presets = SelectView::new()
.with_all(
crate::pronouns::make_presets()
.into_iter()
.map(|pronouns| (pronouns.format_full(), pronouns)),
)
.on_select(|siv, pronouns| update_pronouns_edit(siv, pronouns))
.scrollable();
let layout = LinearLayout::horizontal()
.child(Panel::new(presets).title("Presets"))
.child(Panel::new(example).title("Example Usage"));
let dialog = Dialog::around(layout)
.title("Select Pronouns")
.button("Custom...", |siv| {
siv.pop_layer();
edit_pronouns(siv);
})
.dismiss_button("Ok")
.button("None", |siv| {
siv.call_on_name("pronouns_button", |view: &mut Button| {
view.set_label("none");
})
.unwrap();
siv.with_user_data(|form: &mut LoginForm| {
form.pronouns = None;
});
siv.pop_layer();
});
siv.add_layer(dialog);
}
fn get_edit_pronouns(siv: &mut Cursive) -> Pronouns {
let case_sensitive = get_checkbox_contents(siv, "case_sensitive_edit");
let plural = get_checkbox_contents(siv, "plural_edit");
let subject = get_edit_contents(siv, "subject_edit");
let object = get_edit_contents(siv, "object_edit");
let possessive = get_edit_contents(siv, "possessive_edit");
let possessive_pronoun = get_edit_contents(siv, "possessive_pronoun_edit");
let reflexive = get_edit_contents(siv, "reflexive_edit");
Pronouns {
case_sensitive,
plural,
subject,
object,
possessive,
possessive_pronoun,
reflexive,
}
}
pub fn edit_pronouns(siv: &mut Cursive) {
let labels = make_vertical_labels(&[
"Case-sensitive:",
"Plural:",
"Subject:",
"Object:",
"Possessive:",
"Possessive pronoun:",
"Reflexive:",
])
.fixed_width(20);
let mut values = LinearLayout::vertical();
let checkboxes = &["case_sensitive_edit", "plural_edit"];
for name in checkboxes.iter() {
values.add_child(
Checkbox::new()
.on_change(|siv, _value| {
let pronouns = get_edit_pronouns(siv);
update_pronouns_edit(siv, &pronouns);
})
.with_name(*name),
);
}
let edit_views = &[
"subject_edit",
"object_edit",
"possessive_edit",
"possessive_pronoun_edit",
"reflexive_edit",
];
for name in edit_views.iter() {
values.add_child(
EditView::new()
.on_edit(|siv, _text, _cursor| {
let pronouns = get_edit_pronouns(siv);
update_pronouns_edit(siv, &pronouns);
})
.with_name(*name),
);
}
let edit_layout = Panel::new(
LinearLayout::horizontal()
.child(labels)
.child(values.fixed_width(12)),
);
let example = make_example_usage_panel();
let layout = LinearLayout::horizontal().child(edit_layout).child(example);
let dialog = Dialog::around(layout)
.title("Edit Pronouns")
.button("Ok", |siv| {
let pronouns = get_edit_pronouns(siv);
update_pronouns_edit(siv, &pronouns);
siv.pop_layer();
})
.dismiss_button("Cancel");
siv.add_layer(dialog);
}
pub fn show_main(siv: &mut Cursive) {
let messages = LinearLayout::vertical()
.child(TextView::new("Hello, world!"))
.with_name("messages_list");
let messages = ScrollView::new(messages)
.scroll_strategy(ScrollStrategy::StickToBottom)
.with_name("messages_list_scroll")
.full_height();
let message_edit = EditView::new()
.on_submit(|siv, text| {
siv.call_on_name("message_edit", |message: &mut EditView| {
message.set_content("");
});
siv.with_user_data(|message_sender: &mut Sender<String>| {
message_sender.send(text.to_string()).unwrap();
});
add_message(
siv,
&Message {
sender: "me".to_string(),
contents: text.to_owned(),
},
);
})
.with_name("message_edit")
.full_width();
let message_edit = Panel::new(message_edit)
.title("Send Message")
.title_position(HAlign::Left);
let upload_button = Button::new("Upload", |siv| {
siv.add_layer(Dialog::info("Uploading system goes here"))
});
let message_buttons = Panel::new(upload_button);
let message_bar = LinearLayout::horizontal()
.child(message_edit)
.child(message_buttons);
let chat = LinearLayout::vertical().child(messages).child(message_bar);
let chat = Panel::new(chat).title("Chat");
let mut rooms = SelectView::<&'static str>::new().on_submit(|siv, room: &str| {
let dialog = Dialog::info(format!("Selected room: {}", room));
siv.add_layer(dialog);
});
rooms.add_item("Room 1", "room_id_1");
rooms.add_item("Room 2", "room_id_2");
rooms.add_item("Room 3", "room_id_3");
let rooms = Dialog::around(rooms)
.button("Create...", |siv| {
siv.add_layer(Dialog::info("Room creation dialog goes here"))
})
.title("Rooms")
.title_position(HAlign::Left)
.with_name("room_select");
let connections = LinearLayout::vertical().with_name("connections_list");
let connections = Dialog::around(connections)
.button("Add...", |siv| show_create_connection(siv))
.title("Connections")
.title_position(HAlign::Left);
let sidebar = LinearLayout::vertical()
.child(rooms)
.child(connections)
.fixed_width(24);
let layout = LinearLayout::horizontal().child(sidebar).child(chat);
siv.add_fullscreen_layer(layout);
}
fn show_create_connection(siv: &mut Cursive) {
let labels = make_vertical_labels(&["IP:Port:"]);
let values = EditView::new().with_name("address_edit").min_width(20);
let layout = LinearLayout::horizontal().child(labels).child(values);
let dialog = Dialog::around(layout)
.title("Create Connection")
.button("Ok", |siv| {
let address = get_edit_contents(siv, "address_edit");
let address = match address.parse() {
Ok(address) => address,
Err(err) => {
let message = format!("{:?}", err);
let dialog = Dialog::info(message).title("Create Connection Error");
return siv.add_layer(dialog);
}
};
let err = siv.with_user_data(|state: &mut State| state.connect(address).err());
if let Some(Some(err)) = err {
let message = format!("{:?}", err);
let dialog = Dialog::info(message).title("Connection Error");
return siv.add_layer(dialog);
}
siv.pop_layer();
})
.dismiss_button("Cancel");
siv.add_layer(dialog);
}
fn get_edit_contents(siv: &mut Cursive, name: &str) -> String {
siv.call_on_name(name, |view: &mut EditView| view.get_content())
.unwrap()
.to_string()
}
fn get_checkbox_contents(siv: &mut Cursive, name: &str) -> bool {
siv.call_on_name(name, |view: &mut Checkbox| view.is_checked())
.unwrap()
}
fn make_vertical_labels(labels: &[&str]) -> LinearLayout {
let mut layout = LinearLayout::vertical();
for label in labels.iter() {
layout.add_child(TextView::new(label.to_string()));
}
layout
}