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 if !no_native && window_inner.supports_native_menu_bar() {
212 window_inner.setup_menubar(menubar);
213 return Value::Void;
214 }
215
216 *current.menubar.borrow_mut() = Some(menubar);
218
219 wire_menu_from_item_tree(ctx, entries_ref, sub_menu_ref, activated_ref, menu_item_tree);
221
222 Value::Void
223}
224
225pub(crate) fn setup_system_tray_icon(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
226 let [
231 Expression::PropertyReference(system_tray_ref),
232 Expression::NumberLiteral(tree_index),
233 rest @ ..,
234 ] = arguments
235 else {
236 return Value::Void;
237 };
238
239 let current = match ctx.current.as_ref() {
240 Some(c) => c.clone(),
241 None => return Value::Void,
242 };
243 let cu = current.compilation_unit.clone();
244 let sc = &cu.sub_components[current.sub_component_idx];
245 let Some(menu_tree) = sc.menu_item_trees.get(*tree_index as usize) else {
246 return Value::Void;
247 };
248
249 let menu_vrc = new_popup_for(¤t, menu_tree);
250 crate::instance::finalize_instance(&menu_vrc);
251
252 let condition: Box<dyn Fn() -> bool> = match rest.first() {
255 Some(expr) => Box::new(bool_binding(¤t, expr)),
256 None => Box::new(|| true),
257 };
258 let menu_item_tree =
259 vtable::VRc::new(i_slint_core::menus::MenuFromItemTree::new_with_condition_and_visible(
260 vtable::VRc::into_dyn(menu_vrc),
261 condition,
262 || true,
263 ));
264
265 let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, system_tray_ref) else {
266 return Value::Void;
267 };
268 let item_rc =
269 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(parent_inst), flat_idx as u32);
270 if let Some(tray) = item_rc.downcast::<i_slint_core::items::SystemTrayIcon>() {
271 tray.as_pin_ref().set_menu(&item_rc, vtable::VRc::into_dyn(menu_item_tree));
272 }
273 Value::Void
274}
275
276pub(crate) fn show_popup_menu(ctx: &mut EvalContext, arguments: &[Expression]) -> Value {
277 let [Expression::PropertyReference(context_menu_ref), entries_expr, position_expr] = arguments
278 else {
279 return Value::Void;
280 };
281
282 let position: i_slint_core::api::LogicalPosition =
283 eval_expression(ctx, position_expr).try_into().unwrap_or_default();
284
285 let Some((parent_inst, context_flat_idx)) = resolve_item_rc_from_ref(ctx, context_menu_ref)
286 else {
287 return Value::Void;
288 };
289 let context_item_rc = i_slint_core::items::ItemRc::new(
290 vtable::VRc::into_dyn(parent_inst.clone()),
291 context_flat_idx as u32,
292 );
293 let context_menu_item_weak = context_item_rc.downgrade();
294 let Some(adapter) = find_window_adapter(ctx) else {
295 return Value::Void;
296 };
297
298 let cu = ctx.current.as_ref().map(|c| c.compilation_unit.clone()).unwrap();
299 let Some(popup_menu) = cu.popup_menu.as_ref() else {
300 return Value::Void;
301 };
302
303 let current = ctx.current.as_ref().unwrap();
304 let popup_vrc = new_popup_for(current, &popup_menu.item_tree);
305 crate::instance::install_bindings_for_repeated_row(&popup_vrc);
309
310 let popup_ctx = crate::eval::EvalContext::new(popup_vrc.root_sub_component.clone());
319
320 if let Expression::NumberLiteral(tree_index) = entries_expr {
321 let sc = &cu.sub_components[current.sub_component_idx];
322 let Some(menu_tree) = sc.menu_item_trees.get(*tree_index as usize) else {
323 return Value::Void;
324 };
325 let menu_vrc = crate::instance::Instance::new_popup(
326 cu.clone(),
327 menu_tree,
328 std::rc::Rc::downgrade(&Pin::into_inner(current.clone())),
329 popup_vrc.globals.clone(),
330 );
331 crate::instance::finalize_instance(&menu_vrc);
332 let menu_item_tree = vtable::VRc::new(i_slint_core::menus::MenuFromItemTree::new(
333 vtable::VRc::into_dyn(menu_vrc),
334 ));
335
336 if i_slint_core::window::WindowInner::from_pub(adapter.window()).show_native_popup_menu(
339 vtable::VRc::into_dyn(vtable::VRc::clone(&menu_item_tree)),
340 position,
341 &context_item_rc,
342 ) {
343 return Value::Void;
344 }
345
346 wire_menu_from_item_tree(
347 &popup_ctx,
348 &popup_menu.entries,
349 &popup_menu.sub_menu,
350 &popup_menu.activated,
351 menu_item_tree,
352 );
353 } else {
354 let entries_value = eval_expression(ctx, entries_expr);
358 wire_popup_menu_prop(&popup_ctx, &popup_menu.entries, move || entries_value.clone());
359
360 if let MemberReference::Relative { parent_level, local_reference } = context_menu_ref {
361 let sub_menu_owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
362 let LocalMemberIndex::Native { item_index, .. } = &local_reference.reference else {
363 return Value::Void;
364 };
365 let item_index_for_sub = *item_index;
366 let sub_menu_owner_weak =
367 std::rc::Rc::downgrade(&Pin::into_inner(sub_menu_owner.clone()));
368 wire_popup_menu_cb(&popup_ctx, &popup_menu.sub_menu, move |args| {
369 let Some(owner) = sub_menu_owner_weak.upgrade() else { return Value::Void };
370 let item = Pin::as_ref(&owner.items[item_index_for_sub]);
371 let entry: i_slint_core::items::MenuEntry =
372 args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
373 let raw = item.as_item_ref();
374 use i_slint_core::items::ContextMenu;
375 let Some(cm) = vtable::VRef::downcast_pin::<ContextMenu>(raw) else {
376 return Value::Void;
377 };
378 let mut out = i_slint_core::SharedVector::default();
379 cm.sub_menu.call(&(entry,)).iter().for_each(|e| out.push(e));
380 Value::Model(i_slint_core::model::ModelRc::new(
381 i_slint_core::model::VecModel::from(
382 out.into_iter().map(Value::from).collect::<Vec<_>>(),
383 ),
384 ))
385 });
386
387 let activated_owner_weak =
388 std::rc::Rc::downgrade(&Pin::into_inner(sub_menu_owner.clone()));
389 let item_index_for_activated = *item_index;
390 wire_popup_menu_cb(&popup_ctx, &popup_menu.activated, move |args| {
391 let Some(owner) = activated_owner_weak.upgrade() else { return Value::Void };
392 let item = Pin::as_ref(&owner.items[item_index_for_activated]);
393 let entry: i_slint_core::items::MenuEntry =
394 args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
395 let raw = item.as_item_ref();
396 use i_slint_core::items::ContextMenu;
397 let Some(cm) = vtable::VRef::downcast_pin::<ContextMenu>(raw) else {
398 return Value::Void;
399 };
400 cm.activated.call(&(entry,));
401 Value::Void
402 });
403 }
404 }
405
406 let popup_id_cell: std::rc::Rc<std::cell::Cell<Option<core::num::NonZeroU32>>> =
411 std::rc::Rc::new(std::cell::Cell::new(None));
412 let id_cell_for_close = popup_id_cell.clone();
413 let adapter_for_close = adapter.clone();
414 wire_popup_menu_cb(&popup_ctx, &popup_menu.close, move |_args| {
415 if let Some(id) = id_cell_for_close.take() {
416 i_slint_core::window::WindowInner::from_pub(adapter_for_close.window()).close_popup(id);
417 }
418 Value::Void
419 });
420
421 let popup_dyn = vtable::VRc::into_dyn(popup_vrc.clone());
422 let window_inner = i_slint_core::window::WindowInner::from_pub(adapter.window());
423 let popup_id = window_inner.show_popup(
424 &popup_dyn,
425 Box::new(move || position),
426 i_slint_core::items::PopupClosePolicy::CloseOnClickOutside,
427 &context_item_rc,
428 i_slint_core::window::WindowKind::Menu,
429 Box::new(|_| {}),
430 );
431 popup_id_cell.set(Some(popup_id));
432 if let Some(item_rc) = context_menu_item_weak.upgrade()
436 && let Some(cm) = item_rc.downcast::<i_slint_core::items::ContextMenu>()
437 {
438 cm.as_pin_ref().popup_id.set(Some(popup_id));
439 }
440
441 crate::instance::finalize_instance(&popup_vrc);
444
445 Value::Void
446}
447
448fn new_popup_for(
452 owner: &Pin<Rc<SubComponentInstance>>,
453 item_tree: &i_slint_compiler::llr::ItemTree,
454) -> vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance> {
455 let cu = owner.compilation_unit.clone();
456 let parent_weak = std::rc::Rc::downgrade(&Pin::into_inner(owner.clone()));
457 let globals = owner
458 .root
459 .get()
460 .and_then(|w| w.upgrade())
461 .map(|inst| inst.globals.clone())
462 .unwrap_or_else(|| std::rc::Rc::new(crate::globals::GlobalStorage::new(&cu)));
463 crate::instance::Instance::new_popup(cu, item_tree, parent_weak, globals)
464}
465
466fn bool_binding(
469 owner: &Pin<Rc<SubComponentInstance>>,
470 expr: &Expression,
471) -> impl Fn() -> bool + 'static {
472 let expr = expr.clone();
473 let weak = std::rc::Rc::downgrade(&Pin::into_inner(owner.clone()));
474 move || {
475 let Some(owner) = weak.upgrade() else { return false };
476 let mut ctx = EvalContext::new(Pin::new(owner));
477 matches!(eval_expression(&mut ctx, &expr), Value::Bool(true))
478 }
479}
480
481fn wire_menu_from_item_tree(
485 ctx: &EvalContext,
486 entries: &MemberReference,
487 sub_menu: &MemberReference,
488 activated: &MemberReference,
489 menu_item_tree: vtable::VRc<
490 i_slint_core::menus::MenuVTable,
491 i_slint_core::menus::MenuFromItemTree,
492 >,
493) {
494 fn entries_model(
495 mt: &i_slint_core::menus::MenuFromItemTree,
496 parent: Option<&i_slint_core::items::MenuEntry>,
497 ) -> Value {
498 let mut entries = i_slint_core::SharedVector::default();
499 i_slint_core::menus::Menu::sub_menu(mt, parent, &mut entries);
500 Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
501 entries.into_iter().map(Value::from).collect::<Vec<_>>(),
502 )))
503 }
504 let mt = vtable::VRc::clone(&menu_item_tree);
505 wire_popup_menu_prop(ctx, entries, move || entries_model(&mt, None));
506 let mt = vtable::VRc::clone(&menu_item_tree);
507 wire_popup_menu_cb(ctx, sub_menu, move |args| {
508 let entry = args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
509 entries_model(&mt, Some(&entry))
510 });
511 wire_popup_menu_cb(ctx, activated, move |args| {
512 let entry = args.first().cloned().unwrap_or_default().try_into().unwrap_or_default();
513 i_slint_core::menus::Menu::activate(&*menu_item_tree, &entry);
514 Value::Void
515 });
516}
517
518fn wire_popup_menu_prop(
519 ctx: &EvalContext,
520 mr: &MemberReference,
521 binding: impl Fn() -> Value + 'static,
522) {
523 if let MemberReference::Relative { parent_level, local_reference } = mr {
524 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
525 if let LocalMemberIndex::Property(idx) = &local_reference.reference {
526 Pin::as_ref(&owner.properties[*idx]).set_binding(binding);
527 }
528 }
529}
530
531fn wire_popup_menu_cb(
532 ctx: &EvalContext,
533 mr: &MemberReference,
534 handler: impl Fn(&[Value]) -> Value + 'static,
535) {
536 if let MemberReference::Relative { parent_level, local_reference } = mr {
537 let owner = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
538 if let LocalMemberIndex::Callback(idx) = &local_reference.reference {
539 Pin::as_ref(&owner.callbacks[*idx]).set_handler(handler);
540 }
541 }
542}