perspective_viewer/ui/
context_menu.rs1use web_sys::MouseEvent;
14use yew::prelude::*;
15
16#[derive(Clone, PartialEq)]
18pub struct ContextMenuItem {
19 pub label: String,
20 pub on_select: Callback<()>,
21 pub disabled: bool,
22}
23
24#[derive(Clone, PartialEq)]
27pub enum ContextMenuEntry {
28 Item(ContextMenuItem),
29 Header(String),
30
31 Custom(Html),
34 Submenu {
35 label: String,
36
37 on_select: Option<Callback<()>>,
38 entries: Vec<ContextMenuEntry>,
39 },
40}
41
42#[derive(Properties, PartialEq)]
43pub struct ContextMenuProps {
44 pub entries: Vec<ContextMenuEntry>,
45
46 #[prop_or_default]
47 pub on_close: Callback<()>,
48}
49
50pub struct ContextMenu;
52
53impl Component for ContextMenu {
54 type Message = ();
55 type Properties = ContextMenuProps;
56
57 fn create(_ctx: &Context<Self>) -> Self {
58 Self
59 }
60
61 fn view(&self, ctx: &Context<Self>) -> Html {
62 let props = ctx.props();
63 let entries = props
64 .entries
65 .iter()
66 .map(|entry| render_entry(entry, &props.on_close));
67
68 html! { <div class="context-menu">{ for entries }</div> }
69 }
70}
71
72fn render_entry(entry: &ContextMenuEntry, on_close: &Callback<()>) -> Html {
73 match entry {
74 ContextMenuEntry::Item(item) => {
75 let disabled = item.disabled;
76 let onclick = {
77 let on_select = item.on_select.clone();
78 let on_close = on_close.clone();
79 Callback::from(move |e: MouseEvent| {
80 e.stop_propagation();
81 if !disabled {
82 on_select.emit(());
83 on_close.emit(());
84 }
85 })
86 };
87
88 let mut class = classes!("context-menu-item");
89 if disabled {
90 class.push("disabled");
91 }
92
93 html! { <span {class} {onclick}>{ item.label.clone() }</span> }
94 },
95 ContextMenuEntry::Header(label) => {
96 html! { <span class="context-menu-header">{ label.clone() }</span> }
97 },
98 ContextMenuEntry::Custom(content) => content.clone(),
99 ContextMenuEntry::Submenu {
100 label,
101 on_select,
102 entries,
103 } => {
104 let onclick = {
105 let on_select = on_select.clone();
106 let on_close = on_close.clone();
107 Callback::from(move |e: MouseEvent| {
108 e.stop_propagation();
109 if let Some(on_select) = &on_select {
110 on_select.emit(());
111 on_close.emit(());
112 }
113 })
114 };
115
116 let children = entries.iter().map(|entry| render_entry(entry, on_close));
117 html! {
118 <span class="context-menu-item has-submenu" {onclick}>
119 { label.clone() }
120 <div class="context-menu context-menu-submenu">{ for children }</div>
121 </span>
122 }
123 },
124 }
125}