Add help display for menus

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/20/head
trivernis 3 years ago
parent fc1366668f
commit bb153a10a5
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

5
Cargo.lock generated

@ -2347,6 +2347,7 @@ dependencies = [
"thiserror",
"tokio",
"trigram",
"typemap_rev",
]
[[package]]
@ -2535,9 +2536,9 @@ dependencies = [
[[package]]
name = "typemap_rev"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335fb14412163adc9ed4a3e53335afaa7a4b72bdd122e5f72f51b8f1db1a131e"
checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155"
[[package]]
name = "typenum"

@ -36,4 +36,5 @@ reqwest = "0.11.2"
chrono-tz = "0.5.3"
sauce-api = "0.7.1"
rustc_version_runtime = "0.2.0"
trigram = "0.4.4"
trigram = "0.4.4"
typemap_rev = "0.1.5"

@ -1,9 +1,13 @@
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
use crate::menu::container::get_listeners_from_context;
use crate::menu::menu::Menu;
use crate::menu::typedata::HelpActiveContainer;
use serde_json::json;
use serde_json::Value;
use serenity::client::Context;
use serenity::http::CacheHttp;
use serenity::model::channel::Reaction;
use std::sync::atomic::Ordering;
/// Shows the next page in the menu
pub async fn next_page(ctx: &Context, menu: &mut Menu<'_>, _: Reaction) -> SerenityUtilsResult<()> {
@ -47,6 +51,70 @@ pub async fn close_menu(
Ok(())
}
pub async fn toggle_help(
ctx: &Context,
menu: &mut Menu<'_>,
_: Reaction,
) -> SerenityUtilsResult<()> {
log::debug!("Displaying help");
let show_help = menu
.data
.get::<HelpActiveContainer>()
.expect("Missing HelpActiveContainer in menu data")
.clone();
if show_help.load(Ordering::Relaxed) {
display_page(ctx, menu).await?;
show_help.store(false, Ordering::Relaxed);
return Ok(());
}
let page = menu
.pages
.get(menu.current_page)
.ok_or(SerenityUtilsError::PageNotFound(menu.current_page))?
.get()
.await?;
let mut message = menu.get_message(ctx.http()).await?;
let help_message = menu
.help_entries
.iter()
.map(|(e, h)| format!(" - {} {}", e, h))
.collect::<Vec<String>>()
.join("\n");
message
.edit(ctx, |m| {
m.0.clone_from(&mut page.0.clone());
if let Some(embed) = m.0.get_mut("embed") {
let embed = embed.as_object_mut().unwrap();
let fields = embed
.entry("fields")
.or_insert_with(|| Value::Array(vec![]));
if let Value::Array(ref mut inner) = *fields {
inner.push(json!({
"inline": false,
"name": "Help".to_string(),
"value": help_message,
}));
}
} else {
m.embed(|e| {
e.field("Help", help_message, false);
e
});
}
m
})
.await?;
log::debug!("Help message displayed");
show_help.store(true, Ordering::Relaxed);
Ok(())
}
/// Displays the menu page
async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> {
log::debug!("Displaying page {}", menu.current_page);

@ -1,8 +1,9 @@
use crate::core::MessageHandle;
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
use crate::menu::container::get_listeners_from_context;
use crate::menu::controls::{close_menu, next_page, previous_page};
use crate::menu::controls::{close_menu, next_page, previous_page, toggle_help};
use crate::menu::traits::EventDrivenMessage;
use crate::menu::typedata::HelpActiveContainer;
use crate::menu::{EventDrivenMessagesRef, Page};
use futures::FutureExt;
use serenity::async_trait;
@ -10,9 +11,11 @@ use serenity::client::Context;
use serenity::http::Http;
use serenity::model::channel::{Message, Reaction, ReactionType};
use serenity::model::id::ChannelId;
use serenity::prelude::{TypeMap, TypeMapKey};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::{Mutex, RwLock};
@ -20,6 +23,7 @@ use tokio::sync::{Mutex, RwLock};
pub static NEXT_PAGE_EMOJI: &str = "➡️";
pub static PREVIOUS_PAGE_EMOJI: &str = "⬅️";
pub static CLOSE_MENU_EMOJI: &str = "❌";
pub static HELP_EMOJI: &str = "❔";
pub type ControlActionResult<'b> =
Pin<Box<dyn Future<Output = SerenityUtilsResult<()>> + Send + 'b>>;
@ -33,12 +37,12 @@ pub type ControlActionArc = Arc<
#[derive(Clone)]
pub struct ActionContainer {
inner: ControlActionArc,
position: usize,
position: isize,
}
impl ActionContainer {
/// Creates a new control action
pub fn new<F: 'static>(position: usize, callback: F) -> Self
pub fn new<F: 'static>(position: isize, callback: F) -> Self
where
F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b>
+ Send
@ -63,7 +67,6 @@ impl ActionContainer {
}
/// A menu message
#[derive(Clone)]
pub struct Menu<'a> {
pub message: Arc<RwLock<MessageHandle>>,
pub pages: Vec<Page<'a>>,
@ -71,6 +74,8 @@ pub struct Menu<'a> {
pub controls: HashMap<String, ActionContainer>,
pub timeout: Instant,
pub sticky: bool,
pub data: TypeMap,
pub help_entries: HashMap<String, String>,
closed: bool,
listeners: EventDrivenMessagesRef,
}
@ -221,6 +226,8 @@ pub struct MenuBuilder {
controls: HashMap<String, ActionContainer>,
timeout: Duration,
sticky: bool,
data: TypeMap,
help_entries: HashMap<String, String>,
}
impl Default for MenuBuilder {
@ -231,6 +238,8 @@ impl Default for MenuBuilder {
controls: HashMap::new(),
timeout: Duration::from_secs(60),
sticky: false,
data: TypeMap::new(),
help_entries: HashMap::new(),
}
}
}
@ -240,21 +249,35 @@ impl MenuBuilder {
pub fn new_paginator() -> Self {
log::debug!("Creating new paginator");
let mut controls = HashMap::new();
let mut help_entries = HashMap::new();
controls.insert(
PREVIOUS_PAGE_EMOJI.to_string(),
ActionContainer::new(0, |c, m, r| previous_page(c, m, r).boxed()),
);
help_entries.insert(
PREVIOUS_PAGE_EMOJI.to_string(),
"Displays the previous page".to_string(),
);
controls.insert(
CLOSE_MENU_EMOJI.to_string(),
ActionContainer::new(1, |c, m, r| close_menu(c, m, r).boxed()),
);
help_entries.insert(
CLOSE_MENU_EMOJI.to_string(),
"Closes the menu buttons".to_string(),
);
controls.insert(
NEXT_PAGE_EMOJI.to_string(),
ActionContainer::new(2, |c, m, r| next_page(c, m, r).boxed()),
);
help_entries.insert(
NEXT_PAGE_EMOJI.to_string(),
"Displays the next page".to_string(),
);
Self {
controls,
help_entries,
..Default::default()
}
}
@ -278,7 +301,7 @@ impl MenuBuilder {
}
/// Adds a single control to the message
pub fn add_control<S, F: 'static>(mut self, position: usize, emoji: S, action: F) -> Self
pub fn add_control<S, F: 'static>(mut self, position: isize, emoji: S, action: F) -> Self
where
S: ToString,
F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b>
@ -295,7 +318,7 @@ impl MenuBuilder {
pub fn add_controls<S, I>(mut self, controls: I) -> Self
where
S: ToString,
I: IntoIterator<Item = (usize, S, ControlActionArc)>,
I: IntoIterator<Item = (isize, S, ControlActionArc)>,
{
for (position, emoji, action) in controls {
self.controls.insert(
@ -332,6 +355,30 @@ impl MenuBuilder {
self
}
/// Adds data to the menu typemap
pub fn add_data<T>(mut self, value: T::Value) -> Self
where
T: TypeMapKey,
{
self.data.insert::<T>(value);
self
}
/// Adds a help entry
pub fn add_help<S: ToString>(mut self, button: S, help: S) -> Self {
self.help_entries
.insert(button.to_string(), help.to_string());
self
}
/// Turns showing help for buttons on
pub fn show_help(self) -> Self {
self.add_control(100, HELP_EMOJI, |c, m, r| Box::pin(toggle_help(c, m, r)))
.add_data::<HelpActiveContainer>(Arc::new(AtomicBool::new(false)))
}
/// builds the menu
pub async fn build(
self,
@ -371,6 +418,8 @@ impl MenuBuilder {
closed: false,
listeners: Arc::clone(&listeners),
sticky: self.sticky,
data: self.data,
help_entries: self.help_entries,
};
log::debug!("Storing menu to listeners...");

@ -3,6 +3,7 @@ pub(crate) mod controls;
pub(crate) mod menu;
pub(crate) mod page;
pub(crate) mod traits;
pub(crate) mod typedata;
pub use container::*;
pub use controls::*;

@ -0,0 +1,9 @@
use serenity::prelude::TypeMapKey;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub struct HelpActiveContainer;
impl TypeMapKey for HelpActiveContainer {
type Value = Arc<AtomicBool>;
}

@ -120,10 +120,11 @@ impl EventHandler for Handler {
if let Some(count) = member_count {
log::debug!("{} Members in channel", count);
let queue = get_queue_for_guild(&ctx, &guild_id).await.unwrap();
let mut queue_lock = queue.lock().await;
log::debug!("Setting leave flag to {}", count == 0);
queue_lock.leave_flag = count == 0;
if let Ok(queue) = get_queue_for_guild(&ctx, &guild_id).await {
let mut queue_lock = queue.lock().await;
log::debug!("Setting leave flag to {}", count == 0);
queue_lock.leave_flag = count == 0;
}
}
}
}

@ -22,6 +22,7 @@ pub async fn create_gifs_menu(
MenuBuilder::new_paginator()
.timeout(Duration::from_secs(120))
.add_pages(pages)
.show_help()
.build(ctx, channel_id)
.await?;

@ -37,15 +37,23 @@ pub async fn create_now_playing_msg(
.add_control(0, STOP_BUTTON, |c, m, r| {
Box::pin(stop_button_action(c, m, r))
})
.add_help(STOP_BUTTON, "Stops the music and leaves the channel")
.add_control(1, PAUSE_BUTTON, |c, m, r| {
Box::pin(play_pause_button_action(c, m, r))
})
.add_help(PAUSE_BUTTON, "Pauses the music")
.add_control(2, SKIP_BUTTON, |c, m, r| {
Box::pin(skip_button_action(c, m, r))
})
.add_help(SKIP_BUTTON, "Skips to the next song")
.add_control(3, GOOD_PICK_BUTTON, |c, m, r| {
Box::pin(good_pick_action(c, m, r))
})
.add_help(
GOOD_PICK_BUTTON,
"Remembers this video for spotify-youtube mappings",
)
.show_help()
.add_page(Page::new_builder(move || {
let queue = Arc::clone(&queue);
Box::pin(async move {

@ -38,6 +38,7 @@ pub async fn show_sauce_menu(
MenuBuilder::new_paginator()
.timeout(Duration::from_secs(600))
.add_pages(pages)
.show_help()
.build(ctx, msg.channel_id)
.await?;
}

Loading…
Cancel
Save