-
Notifications
You must be signed in to change notification settings - Fork 711
NOT FUNCTIONAL: muda
context menu support
#8803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This represents the better part of a day of trying to implement native context menu support with `muda`. I've been able to get a context menu to appear, with the following caveats: - Only the Rust code generator is supported - Only Windows is supported - Menu activation is `todo!()` - Root menu items seem to display a subitem whether they have items or not I've been finding it challenging navigating the Slint object model, particularly in the context of code generation, and I'm sure in some cases I'm overlooking simpler solutions. So I was hoping that I could get some feedback (perhaps what objects I need to "lock on to") about how to pull this over the finish line. Thanks in advance, I'm really looking forward to having a native context menu in Slint!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution!
Great work so far.
There are two kind of menus:
Simple menus that are entirely computed at compile time, or more complex menu (if let Expression::NumberLiteral(tree_index) = entries
in the rust.rs code) which are a item tree.
Complex menu are used if if
or for
are being used within the menu.
To show a native menu, you will need, as you've already discovered , a VBox<MenuVTable>
which is basically an equivalent of Box<dyn Menu>
This VBox/VTable dance is necessary because this might be generated by C++ code. and therefore everything need to be done with extern "C"
and repr(C) type. Hence the use of the vtable
trick.
I wisth at some point we could have "native" trait object in rust that can be shared with ffi.
To get the that VBox<MenuVTable>
for complex menu, you can use MenuFromItemTree
that there is already there:
slint/internal/compiler/generator/rust.rs
Line 2811 in ff0d450
let context_menu_item_tree = sp::Rc::new(sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance))); |
Currently this part is not in your patch.
The part you have in your patch (rust.rs) is for simple menu that have a generated activate and sub_menu. What you need to do is to have a wrapper like the one there:
slint/internal/compiler/generator/rust.rs
Line 3143 in ff0d450
struct MenuBarWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>); |
This should be a bit simpler than the ContextMenuFromItemTree you have currently
Only the Rust code generator is supported
I think it is fine to start small. Further generator can be handled later, as long as they don't break.
Only Windows is supported
Same here, mac can be done in a follow up. It just need to fallback correctly if not implemented.
I've been finding it challenging navigating the Slint object model, particularly in the context of code generation, and I'm sure in some cases I'm overlooking simpler solutions. So I was hoping that I could get some feedback (perhaps what objects I need to "lock on to") about how to pull this over the finish line.
I hope that the explanation above clarifies some of the things.
If not, please ask for questions for clarifications.
Thanks in advance, I'm really looking forward to having a native context menu in Slint!
And thanks for contributing
internal/core/window.rs
Outdated
@@ -1150,6 +1157,17 @@ impl WindowInner { | |||
} | |||
} | |||
|
|||
/// Show a native context menu | |||
pub fn show_context_menu( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is already a stub function called show_native_popup_menu
declared a bit down but that was never functional. I believe you could try to re-use that.
I think the function (also in the WindowAdapter) deserve to have native
in the name to distinguish it from the function that show the slint based context menu.
} | ||
} | ||
} | ||
|
||
#[cfg(muda)] | ||
#[derive(Clone, Copy, Debug)] | ||
pub enum MudaType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably best to move that to muda.rs
} | ||
#[cfg(target_os = "macos")] | ||
{ | ||
todo!(); // unsafe { menu.show_context_menu_for_nsview(nsview, position) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this function returned an option, you could return None there. And WindowAdatper::show_context_menu could return false to tell the runtime to fallback to the Slint based menu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose I was assuming (perhaps incorrectly) that the macOS version of the call was a relatively simple call, and that it in practice it might be easier to just identify the right call than it would be to build a bunch of scaffolding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nvm - saw your next comment
internal/compiler/generator/rust.rs
Outdated
let native_impl = if *no_native { | ||
quote!() | ||
} else { | ||
quote!(if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-using the supports_native_menu_bar for the context menu might not be needed. I can imagine platform having a native context menu but no native menubar or the opposite.
I'd prefer if the show_context_menu would return false for a fallback.
@@ -303,6 +306,7 @@ fn process_context_menu( | |||
index: 0, | |||
ty: crate::typeregister::logical_point_type(), | |||
}, | |||
Expression::BoolLiteral(no_native_menu), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The no_native_menu was done for the live preview because we don't want to replace the live previews's menubar by the previewed menubar.
I think it's fine, and even desirable, if the live preview show the native context menu.
I added some comments there that I hope would help 73dc1c7 |
- Removed the `show_context_menu()` and started using the existing `show_native_popup_menu()` stub. Changed (probably erroneously) the type of the context menu parameter to what was on original change. - Created a `ContextMenuWrapper` for the case where have the generated `activate()` and `sub_menu()` calls (this is incomplete) - Removed the `no_native_menu` infrastructure that I cargo culted.
Thanks for the pointers - I've made the following changes:
I did not move |
internal/compiler/generator/rust.rs
Outdated
None => #entries, | ||
Some(parent) => { | ||
// PROBLEM - how do I wrie this up to the submenu? | ||
// #access_sub_menu.call(&(parent.clone(),)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that line should work. Do you get a comilation error there? Same for activate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When uncommenting that on sub_menu()
I get:
error[E0308]: mismatched types
|
3946 | ... ) . apply_pin (_self) . call (& (parent . clone () ,)) }
| --------- ^^^^^ expected `Pin<&InnerPopupMenuImpl_root_26>`, found `Pin<&InnerMainWindow>`
| |
| arguments to this method are incorrect
|
= note: expected struct `Pin<&InnerPopupMenuImpl_root_26>`
found struct `Pin<&InnerMainWindow>`
note: method defined here
--> C:\Users\Nate\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\field-offset-0.3.6\src\lib.rs:264:12
|
264 | pub fn apply_pin<'a>(self, x: Pin<&'a T>) -> Pin<&'a U> {
| ^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
I get a virtually identical error when uncommenting that on activate()
:
error[E0308]: mismatched types
|
3959 | ... ) . apply_pin (_self) . call (& (entry . clone () ,)) }
| --------- ^^^^^ expected `Pin<&InnerPopupMenuImpl_root_26>`, found `Pin<&InnerMainWindow>`
| |
| arguments to this method are incorrect
|
= note: expected struct `Pin<&InnerPopupMenuImpl_root_26>`
found struct `Pin<&InnerMainWindow>`
note: method defined here
--> C:\Users\Nate\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\field-offset-0.3.6\src\lib.rs:264:12
|
264 | pub fn apply_pin<'a>(self, x: Pin<&'a T>) -> Pin<&'a U> {
| ^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
I assume there is a straight forward way to walk the object hierarchy to get what we want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the problem, access_sub_menu / access_activated are in the context of the menu.
So the change i suggest might help.
internal/compiler/generator/rust.rs
Outdated
let fw_activated = forward_callback(access_activated, quote!(activated)); | ||
quote! { | ||
let context_menu_item_tree = if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() { | ||
// May seem overkill to have an instance of the struct for each call, but there should only be one call per component anyway | ||
struct ContextMenuWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct ContextMenuWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>); | |
struct ContextMenuWrapper(sp::VWeak<sp::ItemTreeVTable, #popup_id>); |
internal/compiler/generator/rust.rs
Outdated
None => #entries, | ||
Some(parent) => { | ||
// PROBLEM - how do I wrie this up to the submenu? | ||
// #access_sub_menu.call(&(parent.clone(),)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the problem, access_sub_menu / access_activated are in the context of the menu.
So the change i suggest might help.
internal/compiler/generator/rust.rs
Outdated
todo!() | ||
} | ||
} | ||
Some(sp::VBox::new(ContextMenuWrapper(_self.self_weak.get().unwrap().clone()))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some(sp::VBox::new(ContextMenuWrapper(_self.self_weak.get().unwrap().clone()))) | |
Some(sp::VBox::new(ContextMenuWrapper(sp::VRc::downgrade(&popup_instance)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to do something like that; it wasn't clear what I need to change sp::ItemTreeVTable
to in this context:
struct ContextMenuWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>);
- Root menu items seem to be empty submenus, and are this not usable - Root separators seem to be blank menu items - `ContextMenuWrapper` works by virtue of passing `Box<dyn ...>` callbacks to them. This is clearly a hack to get around the challenges of using the "quote macros" from within `impl ContextMenuWrapper` This is clearly not a complete and desirable solution (yet)
Just pushed a few (marginal) improvements; details in the commit |
Screenshot of test program showing this, and accompanying Slint code import { Button, HorizontalBox } from "std-widgets.slint";
export component MainWindow inherits Window {
width: 300px;
height: 200px;
in-out property <string> my-text;
Text {
text: my-text;
}
ContextMenuArea {
Menu {
MenuItem {
title: @tr("Cut");
activated => {
my-text = "Cut";
}
}
MenuItem {
title: @tr("Copy");
activated => {
my-text = "Copy";
}
}
MenuItem {
title: @tr("Paste");
activated => {
my-text = "Paste";
}
}
MenuSeparator { }
Menu {
title: @tr("Find");
MenuItem {
title: @tr("Find Next");
activated => {
my-text = "Find Next";
}
}
MenuItem {
title: @tr("Find Previous");
activated => {
my-text = "Find Previous";
}
}
}
}
}
} |
I think the reason why the root is always a sub-menu is because of the depth!=0 in this line: slint/internal/backends/winit/muda.rs Lines 90 to 91 in 0a25de3
I think this was needed for the menu bar in the past, but maybe now this comment is obsolete and the depth!=0 can be removed. |
internal/compiler/generator/rust.rs
Outdated
// and access_activated from within ContextMenuWrapper | ||
type PopupAccessSubMenu = Box::<dyn Fn(&sp::MenuEntry) -> sp::ModelRc<sp::MenuEntry> + 'static>; | ||
type PopupActivate = Box::<dyn Fn(&sp::MenuEntry) + 'static>; | ||
struct ContextMenuWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>, PopupAccessSubMenu, PopupActivate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did my suggestion to use the #popup_id instead of #inner_component_id not work?
- Removed awkward Box<dyn> usage - Using sp::VRc<sp::ItemTreeVTable> instead of recommended sp::VWeakMapped<sp::ItemTreeVTable> because the weak reference does not live enough to be activated Outstanding issues: - Root items are incorrectly all sub menus - No longer needed ContextMenuItemTree needs to be removed - MudaType needs to be moved
I missed that part (doh!). I was able to get your suggestion to work, only with the catch that I couldn't use the weak reference because it didn't survive long enough for the context menu to be activated. Outstanding issues:
|
Seemed to do the trick! |
internal/compiler/generator/rust.rs
Outdated
let parent_weak = _self.self_weak.get().unwrap().clone(); | ||
#init_popup | ||
|
||
if !sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_native_popup_menu(context_menu_item_tree.unwrap(), #position) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the .unwrap() here seems bad, as it would panic when supports_native_menu_bar returns false.
{ | ||
let _self = popup_instance_vrc.as_pin_ref(); | ||
#access_entries.set(entries); | ||
#access_entries.set(entries.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to clone here?
else { | ||
None | ||
}; | ||
|
||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only need this part when not using the native context menu (fallback)
#context_menu.popup_id.set(Some(id)); | ||
#popup_id::user_init(popup_instance_vrc); | ||
} | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that you only take care of the else
branch of the if
that ends here. But the other branch is for when the menu contains if _: MenuItem{}
or for _ in _ : MenuItem{}
There, the context_menu_item_tree is in a Rc instead of a VBox. It might be worth putting them in a VRc in both branches.
- Removed `ContextMenuFromItemTree` - Moved `MudaType` to `internal/backends/winit/muda.rs` - Stopped unnecessary use of `Option` when creating `context_menu_item_tree`
Committed a few more changes! Regarding this feedback:
Separately I also tried to investigate why the separator appears as a blank entry. Despite the Slint entry being a |
If the code is compatible then it's not a problem. But since show_native_popup_menu takes a VBox, it might not be compatible.
Right, VRc would need extra function in the MenuVTable. |
…VBox<MenuVTable> -- Problem: is using `vtable::VRc::borrow(&menubar)` the correct approach? -- Problem: `VTableMetaDropInPlace` is not properly implemented for `MenuVTable` -- Problem: Codegen for menus has not been updated yet - Updated Context Menu code to use `VRc` so both sides are supported
Pushed more changes; got both sides supported by using
I didn't figure out how to do this, so I created a bogus implementation of |
You need to add a |
I fixed the To my knowledge there are two outstanding issues:
|
I'm curious what the next steps are? |
The Next step is to get the CI to pass so we can merge this. The current error is (while compiling the slint-interpreter crate)
That was just a stub in eval.rs and that code can be removed (then it still won't use the native context menu in the live preview), or adapted to your change. If that's too much to ask, I can also take this over this change, but it'll take some time as I'll be busy/vacations the next weeks. Feel free to ask any questions otherwise and thanks a lot for your contributions. |
I fixed the compilation problems on the The /// Setup the native menu bar
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_windowrc_setup_native_menu_bar(
handle: *const WindowAdapterRcOpaque,
vtable: NonNull<MenuVTable>,
menu_instance: NonNull<c_void>,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter
.internal(crate::InternalToken)
.map(|x| x.setup_menubar(vtable::VBox::from_raw(vtable, menu_instance.cast())));
} I'm very inclined to leave the C++ codegen to the pros... enjoy your vacation! |
This represents the better part of a day of trying to implement native context menu support with
muda
. I've been able to get a context menu to appear, with the following caveats:todo!()
I've been finding it challenging navigating the Slint object model, particularly in the context of code generation, and I'm sure in some cases I'm overlooking simpler solutions. So I was hoping that I could get some feedback (perhaps what objects I need to "lock on to") about how to pull this over the finish line.
Thanks in advance, I'm really looking forward to having a native context menu in Slint!