1use std::sync::Arc;
4
5use crate::callback::Callback;
6use crate::core::element::{Element, IntoElement};
7use crate::style::{BorderStyle, Length, Padding, ScrollbarConfig, Style, StyleSlot};
8use crate::widgets::{List, ListItem, Popover, PopoverOffset, PopoverPlacement};
9
10#[derive(Clone)]
12pub struct ContextMenu {
13 trigger: Element,
14 items: Vec<ListItem>,
15 open: bool,
16 on_select: Option<Callback<usize>>,
17 on_close: Option<Callback<()>>,
18 placement: PopoverPlacement,
19 offset: PopoverOffset,
20 clamp: bool,
21 auto_flip: bool,
22 anchor: Option<(u16, u16)>,
23 width: Length,
24 height: Length,
25 border: bool,
26 border_style: BorderStyle,
27 padding: Padding,
28 style: Style,
29 selection_style: StyleSlot,
30 unfocused_selection_style: StyleSlot,
31 item_hover_style: Option<StyleSlot>,
32 selection_symbol: Option<Arc<str>>,
33 selection_symbol_style: Option<Style>,
34 unfocused_selection_symbol_style: Option<Style>,
35 scrollbar: bool,
36 scrollbar_config: ScrollbarConfig,
37}
38
39impl ContextMenu {
40 pub fn new(trigger: impl IntoElement) -> Self {
42 Self {
43 trigger: trigger.into(),
44 items: Vec::new(),
45 open: false,
46 on_select: None,
47 on_close: None,
48 placement: PopoverPlacement::BelowStart,
49 offset: PopoverOffset::ZERO,
50 clamp: true,
51 auto_flip: true,
52 anchor: None,
53 width: Length::Px(20),
54 height: Length::Auto,
55 border: true,
56 border_style: BorderStyle::Plain,
57 padding: Padding::default(),
58 style: Style::default(),
59 selection_style: StyleSlot::Inherit,
60 unfocused_selection_style: StyleSlot::Inherit,
61 item_hover_style: None,
62 selection_symbol: Some("> ".into()),
63 selection_symbol_style: None,
64 unfocused_selection_symbol_style: None,
65 scrollbar: false,
66 scrollbar_config: ScrollbarConfig::default(),
67 }
68 }
69
70 pub fn items(mut self, items: impl IntoIterator<Item = impl Into<ListItem>>) -> Self {
72 self.items = items.into_iter().map(Into::into).collect();
73 self
74 }
75
76 pub fn open(mut self, open: bool) -> Self {
78 self.open = open;
79 self
80 }
81
82 pub fn on_select(mut self, cb: Callback<usize>) -> Self {
84 self.on_select = Some(cb);
85 self
86 }
87
88 pub fn on_close(mut self, cb: Callback<()>) -> Self {
90 self.on_close = Some(cb);
91 self
92 }
93
94 pub fn placement(mut self, placement: PopoverPlacement) -> Self {
96 self.placement = placement;
97 self
98 }
99
100 pub fn offset(mut self, offset: impl Into<PopoverOffset>) -> Self {
102 self.offset = offset.into();
103 self
104 }
105
106 pub fn clamp(mut self, clamp: bool) -> Self {
108 self.clamp = clamp;
109 self
110 }
111
112 pub fn auto_flip(mut self, auto_flip: bool) -> Self {
114 self.auto_flip = auto_flip;
115 self
116 }
117
118 pub fn anchor(mut self, anchor: Option<(u16, u16)>) -> Self {
120 self.anchor = anchor;
121 self
122 }
123
124 pub fn width(mut self, width: Length) -> Self {
126 self.width = width;
127 self
128 }
129
130 pub fn height(mut self, height: Length) -> Self {
132 self.height = height;
133 self
134 }
135
136 pub fn border(mut self, border: bool) -> Self {
138 self.border = border;
139 self
140 }
141
142 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
144 self.border_style = border_style;
145 self
146 }
147
148 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
150 self.padding = padding.into();
151 self
152 }
153
154 pub fn style(mut self, style: Style) -> Self {
156 self.style = style;
157 self
158 }
159
160 pub fn selection_style(mut self, style: Style) -> Self {
162 self.selection_style = StyleSlot::Replace(style);
163 self
164 }
165
166 pub fn extend_selection_style(mut self, style: Style) -> Self {
168 self.selection_style = StyleSlot::Extend(style);
169 self
170 }
171
172 pub fn inherit_selection_style(mut self) -> Self {
174 self.selection_style = StyleSlot::Inherit;
175 self
176 }
177
178 pub fn selection_style_slot(mut self, slot: StyleSlot) -> Self {
180 self.selection_style = slot;
181 self
182 }
183
184 pub fn unfocused_selection_style(mut self, style: Style) -> Self {
186 self.unfocused_selection_style = StyleSlot::Replace(style);
187 self
188 }
189
190 pub fn extend_unfocused_selection_style(mut self, style: Style) -> Self {
192 self.unfocused_selection_style = StyleSlot::Extend(style);
193 self
194 }
195
196 pub fn inherit_unfocused_selection_style(mut self) -> Self {
198 self.unfocused_selection_style = StyleSlot::Inherit;
199 self
200 }
201
202 pub fn unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
204 self.unfocused_selection_style = slot;
205 self
206 }
207
208 pub fn item_hover_style(mut self, style: Style) -> Self {
210 self.item_hover_style = Some(StyleSlot::Replace(style));
211 self
212 }
213
214 pub fn extend_item_hover_style(mut self, style: Style) -> Self {
216 self.item_hover_style = Some(StyleSlot::Extend(style));
217 self
218 }
219
220 pub fn inherit_item_hover_style(mut self) -> Self {
222 self.item_hover_style = Some(StyleSlot::Inherit);
223 self
224 }
225
226 pub fn item_hover_style_slot(mut self, slot: StyleSlot) -> Self {
228 self.item_hover_style = Some(slot);
229 self
230 }
231
232 pub fn selection_symbol(mut self, symbol: Option<impl Into<Arc<str>>>) -> Self {
234 self.selection_symbol = symbol.map(Into::into);
235 self
236 }
237
238 pub fn selection_symbol_style(mut self, style: Style) -> Self {
240 self.selection_symbol_style = Some(style);
241 self
242 }
243
244 pub fn unfocused_selection_symbol_style(mut self, style: Style) -> Self {
246 self.unfocused_selection_symbol_style = Some(style);
247 self
248 }
249
250 pub fn scrollbar(mut self, scrollbar: bool) -> Self {
252 self.scrollbar = scrollbar;
253 self
254 }
255
256 pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
258 self.scrollbar_config = config;
259 self
260 }
261}
262
263impl From<ContextMenu> for Element {
264 fn from(menu: ContextMenu) -> Self {
265 let mut list = List::new()
266 .items(menu.items)
267 .border(menu.border)
268 .border_style(menu.border_style)
269 .padding(menu.padding)
270 .style(menu.style)
271 .selection_symbol_style(
272 menu.selection_symbol_style
273 .unwrap_or(menu.selection_style.explicit_style().unwrap_or_default()),
274 )
275 .unfocused_selection_symbol_style(
276 menu.unfocused_selection_symbol_style
277 .or_else(|| menu.unfocused_selection_style.explicit_style())
278 .unwrap_or(menu.selection_style.explicit_style().unwrap_or_default()),
279 )
280 .selection_symbol(menu.selection_symbol)
281 .scrollbar(menu.scrollbar)
282 .scrollbar_config(menu.scrollbar_config)
283 .width(menu.width)
284 .height(menu.height);
285 list = list
286 .selection_style_slot(menu.selection_style)
287 .unfocused_selection_style_slot(menu.unfocused_selection_style)
288 .item_hover_style_slot(menu.item_hover_style.unwrap_or(menu.selection_style));
289
290 if let Some(cb) = menu.on_select {
291 list = list.on_select(Callback::new(move |ev: crate::widgets::ListEvent| {
292 cb.emit(ev.index)
293 }));
294 }
295
296 Popover::new()
297 .trigger(menu.trigger)
298 .content(list)
299 .open(menu.open)
300 .placement(menu.placement)
301 .offset(menu.offset)
302 .clamp(menu.clamp)
303 .auto_flip(menu.auto_flip)
304 .min_trigger_width(false)
305 .anchor(menu.anchor)
306 .on_close(menu.on_close.unwrap_or_else(|| Callback::new(|_| {})))
307 .into()
308 }
309}