1use crate::Value;
9use crate::eval::{
10 EvalContext, eval_expression, find_window_adapter, resolve_item_rc_from_ref, store_property,
11 walk_to,
12};
13use crate::instance::SubComponentInstance;
14use i_slint_compiler::llr::{Expression, LocalMemberIndex, MemberReference};
15use i_slint_core::model::Model;
16use std::pin::Pin;
17use std::rc::Rc;
18
19pub(crate) fn show_popup_window(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
20 let [
32 Expression::NumberLiteral(popup_index),
33 close_policy_expr,
34 Expression::PropertyReference(owner_ref),
35 Expression::PropertyReference(anchor_ref),
36 is_open_args @ ..,
37 ] = arguments
38 else {
39 return Value::Void;
40 };
41 let MemberReference::Relative { parent_level, local_reference } = owner_ref else {
42 return Value::Void;
43 };
44
45 let current = match ctx.current.as_ref() {
46 Some(c) => c.clone(),
47 None => return Value::Void,
48 };
49 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
50 let cu = owner.compilation_unit.clone();
51 let sc = &cu.sub_components[owner.sub_component_idx];
52 let popup = match sc.popup_windows.get(*popup_index as usize) {
53 Some(p) => p,
54 None => return Value::Void,
55 };
56
57 let popup_vrc = new_popup_for(&owner, &popup.item_tree);
58
59 let close_policy: i_slint_core::items::PopupClosePolicy =
60 eval_expression(ctx, close_policy_expr).try_into().unwrap_or_default();
61
62 let Some((parent_instance, parent_flat)) = resolve_item_rc_from_ref(ctx, anchor_ref) else {
63 return Value::Void;
64 };
65 let parent_item_rc = i_slint_core::items::ItemRc::new(
66 vtable::VRc::into_dyn(parent_instance.clone()),
67 parent_flat as u32,
68 );
69 let Some(adapter) = find_window_adapter(ctx) else {
73 return Value::Void;
74 };
75
76 let window_kind = || {
77 if popup.is_tooltip {
78 i_slint_core::window::WindowKind::ToolTip
79 } else {
80 i_slint_core::window::WindowKind::Popup
81 }
82 };
83 if let Some(child_adapter) = i_slint_core::window::WindowInner::from_pub(adapter.window())
87 .create_child_window_adapter(window_kind())
88 {
89 let _ = popup_vrc.window_adapter.set(child_adapter);
90 }
91
92 crate::instance::install_bindings_for_repeated_row(&popup_vrc);
96
97 let access_position: Box<dyn Fn() -> i_slint_core::api::LogicalPosition> = {
100 let pos_expr = popup.position.borrow().clone();
101 let popup_root = popup_vrc.root_sub_component.clone();
102 Box::new(move || {
103 let mut popup_ctx = EvalContext::new(popup_root.clone());
104 eval_expression(&mut popup_ctx, &pos_expr).try_into().unwrap_or_default()
105 })
106 };
107 let is_open_setter: Box<dyn Fn(bool)> = match is_open_args.first() {
111 Some(Expression::PropertyReference(is_open_ref)) => {
112 let is_open_ref = is_open_ref.clone();
113 let current_weak = std::rc::Rc::downgrade(&Pin::into_inner(current.clone()));
114 Box::new(move |value: bool| {
115 if let Some(current) = current_weak.upgrade() {
116 let ctx = EvalContext::new(Pin::new(current));
117 store_property(&ctx, &is_open_ref, Value::Bool(value));
118 }
119 })
120 }
121 _ => Box::new(|_| {}),
122 };
123 let popup_dyn = vtable::VRc::into_dyn(popup_vrc.clone());
124 let popup_id = i_slint_core::window::WindowInner::from_pub(adapter.window()).show_popup(
125 &popup_dyn,
126 access_position,
127 close_policy,
128 &parent_item_rc,
129 window_kind(),
130 is_open_setter,
131 );
132 if let Some(slot) = owner.popup_ids.get(*popup_index as usize) {
134 slot.set(Some(popup_id));
135 }
136 crate::instance::finalize_instance(&popup_vrc);
139 Value::Void
140}
141
142pub(crate) fn close_popup_window(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
143 let [Expression::NumberLiteral(popup_index), Expression::PropertyReference(parent_ref)] =
148 arguments
149 else {
150 return Value::Void;
151 };
152 let MemberReference::Relative { parent_level, local_reference } = parent_ref else {
153 return Value::Void;
154 };
155 let Some(_current) = ctx.current.as_ref() else { return Value::Void };
156 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
159 let id = owner.popup_ids.get(*popup_index as usize).and_then(|slot| slot.take());
160 if let Some(id) = id
161 && let Some(root_inst) = owner.root.get().and_then(|w| w.upgrade())
162 && let Some(adapter) = root_inst.window_adapter_or_default()
163 {
164 i_slint_core::window::WindowInner::from_pub(adapter.window()).close_popup(id);
165 }
166 Value::Void
167}
168
169pub(crate) fn setup_menubar(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
170 let [
171 Expression::PropertyReference(entries_ref),
172 Expression::PropertyReference(sub_menu_ref),
173 Expression::PropertyReference(activated_ref),
174 Expression::NumberLiteral(tree_index),
175 Expression::BoolLiteral(no_native),
176 condition,
177 visible,
178 ..,
179 ] = arguments
180 else {
181 return Value::Void;
182 };
183 let (tree_index, no_native) = (*tree_index as usize, *no_native);
184
185 let current = match ctx.current.as_ref() {
186 Some(c) => c.clone(),
187 None => return Value::Void,
188 };
189 let cu = current.compilation_unit.clone();
190 let sc = &cu.sub_components[current.sub_component_idx];
191 let Some(menu_tree) = sc.menu_item_trees.get(tree_index) else {
192 return Value::Void;
193 };
194
195 let menu_vrc = new_popup_for(¤t, menu_tree);
196 crate::instance::finalize_instance(&menu_vrc);
197
198 let menu_dyn = vtable::VRc::into_dyn(menu_vrc);
199 let menu_item_tree =
200 vtable::VRc::new(i_slint_core::menus::MenuFromItemTree::new_with_condition_and_visible(
201 menu_dyn,
202 bool_binding(¤t, condition),
203 bool_binding(¤t, visible),
204 ));
205
206 let Some(adapter) = find_window_adapter(ctx) else { return Value::Void };
207 let window_inner = i_slint_core::window::WindowInner::from_pub(adapter.window());
208 let menubar = vtable::VRc::into_dyn(vtable::VRc::clone(&menu_item_tree));
209 window_inner.setup_menubar_shortcuts(vtable::VRc::clone(&menubar));
210
211 *current.menubar.borrow_mut() = Some(vtable::VRc::clone(&menubar));
214
215 if !no_native && window_inner.supports_native_menu_bar() {
216 window_inner.setup_menubar(menubar);
217 return Value::Void;
218 }
219
220 wire_menu_from_item_tree(ctx, entries_ref, sub_menu_ref, activated_ref, menu_item_tree);
222
223 Value::Void
224}
225
226pub(crate) fn setup_system_tray_icon(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
227 let [
232 Expression::PropertyReference(system_tray_ref),
233 Expression::NumberLiteral(tree_index),
234 rest @ ..,
235 ] = arguments
236 else {
237 return Value::Void;
238 };
239
240 let current = match ctx.current.as_ref() {
241 Some(c) => c.clone(),
242 None => return Value::Void,
243 };
244 let cu = current.compilation_unit.clone();
245 let sc = &cu.sub_components[current.sub_component_idx];
246 let Some(menu_tree) = sc.menu_item_trees.get(*tree_index as usize) else {
247 return Value::Void;
248 };
249
250 let menu_vrc = new_popup_for(¤t, menu_tree);
251 crate::instance::finalize_instance(&menu_vrc);
252
253 let condition: Box<dyn Fn() -> bool> = match rest.first() {
256 Some(expr) => Box::new(bool_binding(¤t, expr)),
257 None => Box::new(|| true),
258 };
259 let menu_item_tree =
260 vtable::VRc::new(i_slint_core::menus::MenuFromItemTree::new_with_condition_and_visible(
261 vtable::VRc::into_dyn(menu_vrc),
262 condition,
263 || true,
264 ));
265
266 let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, system_tray_ref) else {
267 return Value::Void;
268 };
269 let item_rc =
270 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(parent_inst), flat_idx as u32);
271 if let Some(tray) = item_rc.downcast::<i_slint_core::items::SystemTrayIcon>() {
272 tray.as_pin_ref().set_menu(&item_rc, vtable::VRc::into_dyn(menu_item_tree));
273 }
274 Value::Void
275}
276
277pub(crate) fn show_popup_menu(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
278 let [Expression::PropertyReference(context_menu_ref), entries_expr, position_expr] = arguments
279 else {
280 return Value::Void;
281 };
282
283 let position: i_slint_core::api::LogicalPosition =
284 eval_expression(ctx, position_expr).try_into().unwrap_or_default();
285
286 let Some((parent_inst, context_flat_idx)) = resolve_item_rc_from_ref(ctx, context_menu_ref)
287 else {
288 return Value::Void;
289 };
290 let context_item_rc = i_slint_core::items::ItemRc::new(
291 vtable::VRc::into_dyn(parent_inst.clone()),
292 context_flat_idx as u32,
293 );
294 let context_menu_item_weak = context_item_rc.downgrade();
295 let Some(adapter) = find_window_adapter(ctx) else {
296 return Value::Void;
297 };
298
299 let cu = ctx.current.as_ref().map(|c| c.compilation_unit.clone()).unwrap();
300 let Some(popup_menu) = cu.popup_menu.as_ref() else {
301 return Value::Void;
302 };
303
304 let current = ctx.current.as_ref().unwrap();
305 let popup_vrc = new_popup_for(current, &popup_menu.item_tree);
306 crate::instance::install_bindings_for_repeated_row(&popup_vrc);
310
311 let popup_ctx = crate::eval::EvalContext::new(popup_vrc.root_sub_component.clone());
320
321 if let Expression::NumberLiteral(tree_index) = entries_expr {
322 let sc = &cu.sub_components[current.sub_component_idx];
323 let Some(menu_tree) = sc.menu_item_trees.get(*tree_index as usize) else {
324 return Value::Void;
325 };
326 let menu_vrc = crate::instance::Instance::new_popup(
327 cu.clone(),
328 menu_tree,
329 std::rc::Rc::downgrade(&Pin::into_inner(current.clone())),
330 popup_vrc.globals.clone(),
331 );
332 crate::instance::finalize_instance(&menu_vrc);
333 let menu_item_tree = vtable::VRc::new(i_slint_core::menus::MenuFromItemTree::new(
334 vtable::VRc::into_dyn(menu_vrc),
335 ));
336
337 if i_slint_core::window::WindowInner::from_pub(adapter.window()).show_native_popup_menu(
340 vtable::VRc::into_dyn(vtable::VRc::clone(&menu_item_tree)),
341 position,
342 &context_item_rc,
343 ) {
344 return Value::Void;
345 }
346
347 wire_menu_from_item_tree(
348 &popup_ctx,
349 &popup_menu.entries,
350 &popup_menu.sub_menu,
351 &popup_menu.activated,
352 menu_item_tree,
353 );
354 } else {
355 let entries_value = eval_expression(ctx, entries_expr);
359 wire_popup_menu_prop(&popup_ctx, &popup_menu.entries, move || entries_value.clone());
360
361 if let MemberReference::Relative { parent_level, local_reference } = context_menu_ref {
362 let sub_menu_owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
363 let LocalMemberIndex::Native { item_index, .. } = &local_reference.reference else {
364 return Value::Void;
365 };
366 let item_index_for_sub = *item_index;
367 let sub_menu_owner_weak =
368 std::rc::Rc::downgrade(&Pin::into_inner(sub_menu_owner.clone()));
369 wire_popup_menu_cb(&popup_ctx, &popup_menu.sub_menu, move |args| {
370 let Some(owner) = sub_menu_owner_weak.upgrade() else { return Value::Void };
371 let item = Pin::as_ref(&owner.items[item_index_for_sub]);
372 let entry: i_slint_core::items::MenuEntry =
373 args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
374 let raw = item.as_item_ref();
375 use i_slint_core::items::ContextMenu;
376 let Some(cm) = vtable::VRef::downcast_pin::<ContextMenu>(raw) else {
377 return Value::Void;
378 };
379 let mut out = i_slint_core::SharedVector::default();
380 cm.sub_menu.call(&(entry,)).iter().for_each(|e| out.push(e));
381 Value::Model(i_slint_core::model::ModelRc::new(
382 i_slint_core::model::VecModel::from(
383 out.into_iter().map(Value::from).collect::<Vec<_>>(),
384 ),
385 ))
386 });
387
388 let activated_owner_weak =
389 std::rc::Rc::downgrade(&Pin::into_inner(sub_menu_owner.clone()));
390 let item_index_for_activated = *item_index;
391 wire_popup_menu_cb(&popup_ctx, &popup_menu.activated, move |args| {
392 let Some(owner) = activated_owner_weak.upgrade() else { return Value::Void };
393 let item = Pin::as_ref(&owner.items[item_index_for_activated]);
394 let entry: i_slint_core::items::MenuEntry =
395 args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
396 let raw = item.as_item_ref();
397 use i_slint_core::items::ContextMenu;
398 let Some(cm) = vtable::VRef::downcast_pin::<ContextMenu>(raw) else {
399 return Value::Void;
400 };
401 cm.activated.call(&(entry,));
402 Value::Void
403 });
404 }
405 }
406
407 let popup_id_cell: std::rc::Rc<std::cell::Cell<Option<core::num::NonZeroU32>>> =
412 std::rc::Rc::new(std::cell::Cell::new(None));
413 let id_cell_for_close = popup_id_cell.clone();
414 let adapter_for_close = adapter.clone();
415 wire_popup_menu_cb(&popup_ctx, &popup_menu.close, move |_args| {
416 if let Some(id) = id_cell_for_close.take() {
417 i_slint_core::window::WindowInner::from_pub(adapter_for_close.window()).close_popup(id);
418 }
419 Value::Void
420 });
421
422 let popup_dyn = vtable::VRc::into_dyn(popup_vrc.clone());
423 let window_inner = i_slint_core::window::WindowInner::from_pub(adapter.window());
424 let popup_id = window_inner.show_popup(
425 &popup_dyn,
426 Box::new(move || position),
427 i_slint_core::items::PopupClosePolicy::CloseOnClickOutside,
428 &context_item_rc,
429 i_slint_core::window::WindowKind::Menu,
430 Box::new(|_| {}),
431 );
432 popup_id_cell.set(Some(popup_id));
433 if let Some(item_rc) = context_menu_item_weak.upgrade()
437 && let Some(cm) = item_rc.downcast::<i_slint_core::items::ContextMenu>()
438 {
439 cm.as_pin_ref().popup_id.set(Some(popup_id));
440 }
441
442 crate::instance::finalize_instance(&popup_vrc);
445
446 Value::Void
447}
448
449fn new_popup_for(
453 owner: &Pin<Rc<SubComponentInstance>>,
454 item_tree: &i_slint_compiler::llr::ItemTree,
455) -> vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance> {
456 let cu = owner.compilation_unit.clone();
457 let parent_weak = std::rc::Rc::downgrade(&Pin::into_inner(owner.clone()));
458 let globals = owner
459 .root
460 .get()
461 .and_then(|w| w.upgrade())
462 .map(|inst| inst.globals.clone())
463 .unwrap_or_else(|| std::rc::Rc::new(crate::globals::GlobalStorage::new(&cu)));
464 crate::instance::Instance::new_popup(cu, item_tree, parent_weak, globals)
465}
466
467fn bool_binding(
470 owner: &Pin<Rc<SubComponentInstance>>,
471 expr: &Expression,
472) -> impl Fn() -> bool + 'static {
473 let expr = expr.clone();
474 let weak = std::rc::Rc::downgrade(&Pin::into_inner(owner.clone()));
475 move || {
476 let Some(owner) = weak.upgrade() else { return false };
477 let mut ctx = EvalContext::new(Pin::new(owner));
478 matches!(eval_expression(&mut ctx, &expr), Value::Bool(true))
479 }
480}
481
482fn wire_menu_from_item_tree(
486 ctx: &EvalContext,
487 entries: &MemberReference,
488 sub_menu: &MemberReference,
489 activated: &MemberReference,
490 menu_item_tree: vtable::VRc<
491 i_slint_core::menus::MenuVTable,
492 i_slint_core::menus::MenuFromItemTree,
493 >,
494) {
495 fn entries_model(
496 mt: &i_slint_core::menus::MenuFromItemTree,
497 parent: Option<&i_slint_core::items::MenuEntry>,
498 ) -> Value {
499 let mut entries = i_slint_core::SharedVector::default();
500 i_slint_core::menus::Menu::sub_menu(mt, parent, &mut entries);
501 Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
502 entries.into_iter().map(Value::from).collect::<Vec<_>>(),
503 )))
504 }
505 let mt = vtable::VRc::clone(&menu_item_tree);
506 wire_popup_menu_prop(ctx, entries, move || entries_model(&mt, None));
507 let mt = vtable::VRc::clone(&menu_item_tree);
508 wire_popup_menu_cb(ctx, sub_menu, move |args| {
509 let entry = args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
510 entries_model(&mt, Some(&entry))
511 });
512 wire_popup_menu_cb(ctx, activated, move |args| {
513 let entry = args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
514 i_slint_core::menus::Menu::activate(&*menu_item_tree, &entry);
515 Value::Void
516 });
517}
518
519fn wire_popup_menu_prop(
520 ctx: &EvalContext,
521 mr: &MemberReference,
522 binding: impl Fn() -> Value + 'static,
523) {
524 if let MemberReference::Relative { parent_level, local_reference } = mr {
525 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
526 if let LocalMemberIndex::Property(idx) = &local_reference.reference {
527 Pin::as_ref(&owner.properties[*idx]).set_binding(binding);
528 }
529 }
530}
531
532fn wire_popup_menu_cb(
533 ctx: &EvalContext,
534 mr: &MemberReference,
535 handler: impl Fn(&[Value]) -> Value + 'static,
536) {
537 if let MemberReference::Relative { parent_level, local_reference } = mr {
538 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
539 if let LocalMemberIndex::Callback(idx) = &local_reference.reference {
540 Pin::as_ref(&owner.callbacks[*idx]).set_handler(handler);
541 }
542 }
543}