freya_components/
context_menu.rs1use freya_core::{
2 integration::ScopeId,
3 layers::Layer,
4 prelude::*,
5};
6use torin::prelude::{
7 CursorPoint,
8 Position,
9};
10
11use crate::menu::Menu;
12
13#[derive(Clone, Copy, PartialEq)]
14pub(crate) enum ContextMenuCloseRequest {
15 None,
16 Pending,
17}
18
19#[derive(Clone, Copy, PartialEq)]
41pub struct ContextMenu {
42 pub(crate) location: State<CursorPoint>,
43 pub(crate) menu: State<Option<(CursorPoint, Menu)>>,
44 pub(crate) close_request: State<ContextMenuCloseRequest>,
45}
46
47impl ContextMenu {
48 pub fn get() -> Self {
52 try_consume_root_context()
53 .expect("ContextMenu requires a `ContextMenuViewer` in an ancestor scope")
54 }
55
56 pub fn is_open() -> bool {
57 try_consume_root_context::<Self>().is_some_and(|c| c.menu.read().is_some())
58 }
59
60 pub fn open(menu: Menu) {
64 let mut this = Self::get();
65 this.menu.set(Some(((this.location)(), menu)));
66 this.close_request.set(ContextMenuCloseRequest::None);
67 }
68
69 pub fn open_from_event(event: &Event<PressEventData>, menu: Menu) {
75 let mut this = Self::get();
76 this.menu.set(Some(((this.location)(), menu)));
77
78 let close_request = match event.data() {
79 PressEventData::Mouse(mouse) if mouse.button == Some(MouseButton::Left) => {
80 ContextMenuCloseRequest::Pending
81 }
82 _ => ContextMenuCloseRequest::None,
83 };
84 this.close_request.set(close_request);
85 }
86
87 pub fn close() {
88 if let Some(mut this) = try_consume_root_context::<Self>() {
89 this.menu.set(None);
90 }
91 }
92}
93
94#[derive(Default, Clone, PartialEq)]
112pub struct ContextMenuViewer {
113 key: DiffKey,
114}
115
116impl KeyExt for ContextMenuViewer {
117 fn write_key(&mut self) -> &mut DiffKey {
118 &mut self.key
119 }
120}
121
122impl ContextMenuViewer {
123 pub fn new() -> Self {
124 Self::default()
125 }
126}
127
128impl ComponentOwned for ContextMenuViewer {
129 fn render(self) -> impl IntoElement {
130 let mut context = use_hook(|| {
131 try_consume_root_context::<ContextMenu>().unwrap_or_else(|| {
132 let state = ContextMenu {
133 location: State::create_in_scope(CursorPoint::default(), ScopeId::ROOT),
134 menu: State::create_in_scope(None, ScopeId::ROOT),
135 close_request: State::create_in_scope(
136 ContextMenuCloseRequest::None,
137 ScopeId::ROOT,
138 ),
139 };
140 provide_context_for_scope_id(state, ScopeId::ROOT);
141 state
142 })
143 });
144
145 rect()
146 .on_global_pointer_move(move |e: Event<PointerEventData>| {
147 context.location.set(e.global_location());
148 })
149 .maybe_child(context.menu.read().clone().map(|(location, menu)| {
150 let location = location.to_f32();
151 rect()
152 .layer(Layer::Overlay)
153 .position(Position::new_global().left(location.x).top(location.y))
154 .child(menu.on_close(move |_| match (context.close_request)() {
155 ContextMenuCloseRequest::None => {
156 context.close_request.set(ContextMenuCloseRequest::Pending);
157 }
158 ContextMenuCloseRequest::Pending => {
159 context.menu.set(None);
160 context.close_request.set(ContextMenuCloseRequest::None);
161 }
162 }))
163 }))
164 }
165
166 fn render_key(&self) -> DiffKey {
167 self.key.clone().or(self.default_key())
168 }
169}