1use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
2
3use crate::Menu;
4
5pub struct List {
6 pub items: Vec<ListItem>,
7 pub style: Option<ListStyle>,
13 _priv: (),
14}
15
16impl List {
17 pub fn new(items: Vec<ListItem>) -> Self {
18 Self {
19 items,
20 style: None,
21 _priv: (),
22 }
23 }
24
25 #[must_use = "builder method consumes self"]
26 pub fn as_grid_with_columns(mut self, columns: u32) -> Self {
27 self.style = Some(ListStyle::GridWithColumns(columns));
28 self
29 }
30
31 #[must_use = "builder method consumes self"]
32 pub fn as_grid(mut self) -> Self {
33 self.style = Some(ListStyle::Grid);
34 self
35 }
36
37 #[must_use = "builder method consumes self"]
38 pub fn as_rows(mut self) -> Self {
39 self.style = Some(ListStyle::Rows);
40 self
41 }
42}
43
44#[non_exhaustive]
45pub enum ListStyle {
46 Rows,
47 Grid,
48 GridWithColumns(u32),
49}
50
51impl ListStyle {
52 pub(crate) fn into_proto(self) -> covey_proto::query_response::ListStyle {
53 use covey_proto::query_response::ListStyle as Proto;
54 match self {
55 ListStyle::Rows => Proto::Rows(()),
56 ListStyle::Grid => Proto::Grid(()),
57 ListStyle::GridWithColumns(columns) => Proto::GridWithColumns(columns),
58 }
59 }
60}
61
62#[derive(Clone)]
64pub struct ListItem {
65 pub title: String,
66 pub description: String,
67 pub icon: Option<Icon>,
68 pub(crate) commands: ListItemCallbacks,
70}
71
72impl ListItem {
73 pub fn new(title: impl Into<String>) -> Self {
74 let title = title.into();
75 Self {
76 title: title.clone(),
77 icon: None,
78 description: String::new(),
79 commands: ListItemCallbacks::new(title),
80 }
81 }
82
83 #[must_use = "builder method consumes self"]
84 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
85 self.description = desc.into();
86 self
87 }
88
89 #[must_use = "builder method consumes self"]
90 pub fn with_icon(mut self, icon: Option<Icon>) -> Self {
91 self.icon = icon;
92 self
93 }
94
95 #[must_use = "builder method consumes self"]
96 pub fn with_icon_name(mut self, name: impl Into<String>) -> Self {
97 self.icon = Some(Icon::Name(name.into()));
98 self
99 }
100
101 #[must_use = "builder method consumes self"]
102 pub fn with_icon_text(mut self, text: impl Into<String>) -> Self {
103 self.icon = Some(Icon::Text(text.into()));
104 self
105 }
106
107 #[doc(hidden)]
112 #[must_use]
113 pub fn add_command(mut self, name: &'static str, callback: ActivationFunction) -> Self {
114 self.commands.add_command(name, callback);
115 self
116 }
117}
118
119#[derive(Debug, Clone)]
120pub enum Icon {
121 Name(String),
122 Text(String),
123}
124
125impl Icon {
126 pub(crate) fn into_proto(self) -> covey_proto::list_item::Icon {
127 use covey_proto::list_item::Icon as Proto;
128 match self {
129 Self::Name(name) => Proto::Name(name),
130 Self::Text(text) => Proto::Text(text),
131 }
132 }
133}
134
135type DynFuture<T> = Pin<Box<dyn Future<Output = T>>>;
160type ActivationFunction = Arc<dyn Fn(Menu) -> DynFuture<()> + Send + Sync>;
161
162#[derive(Clone)]
163pub(crate) struct ListItemCallbacks {
164 commands: HashMap<&'static str, ActivationFunction>,
166 item_title: String,
167}
168
169impl ListItemCallbacks {
170 pub(crate) fn new(title: String) -> Self {
171 Self {
172 commands: HashMap::default(),
173 item_title: title,
174 }
175 }
176
177 pub(crate) fn add_command(&mut self, name: &'static str, callback: ActivationFunction) {
178 self.commands.insert(name, callback);
179 }
180
181 pub(crate) async fn call_command(&self, name: &str, menu: Menu) {
183 if let Some(cmd) = self.commands.get(name) {
184 crate::rank::register_usage(&self.item_title);
185 cmd(menu).await;
186 }
187 }
188
189 pub(crate) fn ids(&self) -> impl Iterator<Item = &'static str> + use<'_> {
190 self.commands.keys().copied()
191 }
192}