So I am trying to build a widget that I can right-click, and it opens a little menu with some buttons to click. I've been following similar examples and concepts in the iced_aw
repo, and I have crafted my own little solution, and I am able to make an overlay menu appear on my mouse when I right click, only problem is that it only shows 1/2 buttons I pass in.
I've tried messing around with the fn layout and the fn draw on the 'overlayed' widget, but I think I don't fully understand what is going on.
And I thought "oh maybe I should just pass in an Element<'_, Message>
instead of the button", but after some troubleshooting, it seemed unable to find a child of the tree when I did it that way. It would fail with index out of bounds: the len is 0 but the index is 0
when it it got to the self.content.as_widget().layout(self.tree, renderer, &limits)
part of fn layout
, more specifically failing when the underlying button tried to index: &mut tree.children[0]
. (assume self.content
is an Element<> type.)
Though the underlying widget's children
method looks normal:
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.rowdata), // the underlying content (not really important for this question)
Tree::new(&self.overlay_state) // the menu content
]
}
Code for the overlay:
use crate::row::{Fake, Status, Style};
use iced::advanced::layout::Limits;
use iced::advanced::{layout, renderer, widget::Tree, widget::Widget, Overlay};
use iced::advanced::{mouse, overlay};
use iced::widget::{button, column, row, text, Button, Row};
use iced::{Border, Color, Element, Length, Point, Shadow, Size, Theme, Vector};
use crate::row::Catalog;
pub struct RowMenuOverlay<'a, 'b, Message, Theme, Renderer>
where
Message: Clone,
Theme: Catalog + button::Catalog,
Renderer: iced::advanced::Renderer + iced::advanced::text::Renderer,
{
// state of underlying widget
state: &'a crate::row::Fake,
button_1: Button<'a, Message, Theme, Renderer>,
button_2: Button<'a, Message, Theme, Renderer>,
// more buttons here
position: Point,
tree: &'b mut Tree,
}
pub struct OverlayButtons<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: Catalog + button::Catalog,
Renderer: iced::advanced::Renderer + iced::advanced::text::Renderer,
{
pub tree: &'a mut Tree,
pub state: &'a mut Fake,
pub delete_button: Element<'a, Message, Theme, Renderer>,
pub quick_swap_button: Element<'a, Message, Theme, Renderer>,
pub position: Point,
}
impl<'a, Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
for OverlayButtons<'a, Message, Theme, Renderer>
where
Message: Clone,
Theme: Catalog + button::Catalog + iced::widget::text::Catalog,
Renderer: iced::advanced::Renderer + iced::advanced::text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
let limits = Limits::new(
Size {
width: 100.0,
height: 135.0,
},
Size {
width: 100.0,
height: 135.0,
},
);
let node = layout::Node::with_children(
Size {
width: 100.0,
// makes the button take up the space
// adding more to this number just makes the button larger, not more space for it...
height: 140.0,
},
vec![
self.delete_button
.as_widget()
.layout(self.tree, renderer, &limits),
self.quick_swap_button
.as_widget()
.layout(self.tree, renderer, &limits),
],
);
// puts the menu where the cursor *SHOULD* be
node.move_to(self.position)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: layout::Layout<'_>,
cursor: mouse::Cursor,
) {
let lay_1 = layout.children().next().unwrap();
self.delete_button.as_widget().draw(
self.tree,
renderer,
theme,
style,
lay_1,
cursor,
&layout.bounds(),
);
// will only show the "quick swap button", if one of these two buttons are being drawn
// regardless if it is ONLY the `self.delete_button`, it will still only show the "swap button"
let lay_2 = layout.children().next().unwrap();
self.quick_swap_button.as_widget().draw(
self.tree,
renderer,
theme,
style,
lay_2,
cursor,
&layout.bounds(),
);
}
}
impl<'a, Message, Theme, Renderer> From<OverlayButtons<'a, Message, Theme, Renderer>>
for iced::advanced::overlay::Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: 'a + Catalog + button::Catalog + iced::widget::text::Catalog,
Renderer: 'a + iced::advanced::Renderer + iced::advanced::text::Renderer,
{
fn from(overlay: OverlayButtons<'a, Message, Theme, Renderer>) -> Self {
Self::new(Box::new(overlay))
}
}
When I create the overlay in the underlying file:
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: layout::Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<iced::advanced::overlay::Element<'b, Message, Theme, Renderer>> {
let bounds = layout.bounds();
let position = Point::new(bounds.center_x(), bounds.center_y());
if !self.show_menu {
return None;
}
let row_state: &mut Fake = tree.state.downcast_mut();
// let position = Point::new(bounds.center_x(), bounds.center_y());
// let position = Point::new(360.0, 430.0);
// TODO the position ^^ needs to be relative to the scrollable, because if you scroll 2x rows, click on #6, it will show
// the menu on #8
Some(
OverlayButtons {
tree: &mut tree.children[1],
state: row_state,
delete_button: button(text("delete")).into(),
quick_swap_button: button(text("Swap!")).into(),
position: self.cursor_pos,
}
.into(),
)
}
(sorry if this is a lot of code, iced has some boilerplate, I tried to only include the important stuff)
So I got this to work. The first problem I had was just a matter of passing in the correct state into certain function. I don't think it was included in the original snippets I posted, but I was passing in self.tree.children[0]
when it was supposed to be self.tree
.
And the other problem I solved through that was indeed passing in the Element<'_, Message, Theme, Renderer>
. But I had the problem of is I passed it into the underlying widget, I could not give it to the overlay widget to use. It would complain that the data is owned by the underlying widget, and it doesn't implement clone or copy. And you are able to just pass in a reference to it, but you are not able to use on_event
for the overlay, since that requires a mutable instance of the widget.
So the solution? Making a function that creates the overlay:
pub fn create_menu<'a, Message, Theme, Renderer>(
delete_msg: Message,
) -> Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: 'a + Catalog + button::Catalog + iced::widget::text::Catalog,
Renderer: 'a + iced::advanced::Renderer + iced::advanced::text::Renderer,
{
column![
button(text("delete")).on_press(delete_msg),
button(text("Edit")),
button(text("Quickswap 1")),
button(text("add to.."))
]
.into()
}
And that function is called in a few places in the code:
as the Element<>
type in the fn new
overlay struct:
pub fn new(...
content: create_menu(delete_msg)
...
In the children
function in the underlying widget:
fn children(&self) -> Vec<Tree> {
Tree::new(&self.row_data) // <-- also `Element<>`
Tree:new(&create_menu::<Message, Theme, Renderer>(self.delete_msg.clone()))
// im not sure why it wanted type params...
and finally inside fn overlay
inside the underlying widget's impl
fn overlay ...
Some ( OverlayButtons { // <--- the type that we are returning
...
content: create_menu(self.delete_msg.clone()),
There were also some other hiccups I experience while making this idea work, such as opening the menu inside the scrollable would not put it in the right spot if I was scrolled down a bit, and some layout issues. But I won't get into detail about those, it's outside the scope of the question, but the code will be reflected on my github project: punge