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, pub about: Option, pub port: Option, pub pronouns: Option, } 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>| { 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("", |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| { 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 }