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