Skip to content

Commit 98ec1bb

Browse files
authored
Add a Popover widget, that shows a widget in an overlay (#100)
This serves a similar purpose to `GtkPopover`. It takes two widgets, one to show normally and one to show in an overlay. This should basically work, though more options could be added here.
1 parent 355e5a9 commit 98ec1bb

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

src/widget/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub use nav_bar::nav_bar;
2323
pub mod nav_bar_toggle;
2424
pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle};
2525

26+
pub mod popover;
27+
pub use popover::{popover, Popover};
28+
2629
pub mod rectangle_tracker;
2730

2831
pub mod search;

src/widget/popover.rs

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Copyright 2022 System76 <[email protected]>
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
//! A widget showing a popup in an overlay positioned relative to another widget.
5+
6+
use iced_native::event::{self, Event};
7+
use iced_native::layout;
8+
use iced_native::mouse;
9+
use iced_native::overlay;
10+
use iced_native::renderer;
11+
use iced_native::widget::{Operation, Tree};
12+
use iced_native::{Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Widget};
13+
use std::cell::RefCell;
14+
15+
pub use iced_style::container::{Appearance, StyleSheet};
16+
17+
pub fn popover<'a, Message, Renderer>(
18+
content: impl Into<Element<'a, Message, Renderer>>,
19+
popup: impl Into<Element<'a, Message, Renderer>>,
20+
) -> Popover<'a, Message, Renderer> {
21+
Popover::new(content, popup)
22+
}
23+
24+
pub struct Popover<'a, Message, Renderer> {
25+
content: Element<'a, Message, Renderer>,
26+
// XXX Avoid refcell; improve iced overlay API?
27+
popup: RefCell<Element<'a, Message, Renderer>>,
28+
}
29+
30+
impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
31+
fn new(
32+
content: impl Into<Element<'a, Message, Renderer>>,
33+
popup: impl Into<Element<'a, Message, Renderer>>,
34+
) -> Self {
35+
Self {
36+
content: content.into(),
37+
popup: RefCell::new(popup.into()),
38+
}
39+
}
40+
41+
// TODO More options for positioning similar to GdkPopup, xdg_popup
42+
}
43+
44+
impl<'a, Message, Renderer> Widget<Message, Renderer> for Popover<'a, Message, Renderer>
45+
where
46+
Renderer: iced_native::Renderer,
47+
Renderer::Theme: StyleSheet,
48+
{
49+
fn children(&self) -> Vec<Tree> {
50+
vec![Tree::new(&self.content), Tree::new(&*self.popup.borrow())]
51+
}
52+
53+
fn diff(&self, tree: &mut Tree) {
54+
tree.diff_children(&[&self.content, &self.popup.borrow()])
55+
}
56+
57+
fn width(&self) -> Length {
58+
self.content.as_widget().width()
59+
}
60+
61+
fn height(&self) -> Length {
62+
self.content.as_widget().height()
63+
}
64+
65+
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
66+
self.content.as_widget().layout(renderer, limits)
67+
}
68+
69+
fn operate(&self, tree: &mut Tree, layout: Layout<'_>, operation: &mut dyn Operation<Message>) {
70+
self.content
71+
.as_widget()
72+
.operate(&mut tree.children[0], layout, operation)
73+
}
74+
75+
fn on_event(
76+
&mut self,
77+
tree: &mut Tree,
78+
event: Event,
79+
layout: Layout<'_>,
80+
cursor_position: Point,
81+
renderer: &Renderer,
82+
clipboard: &mut dyn Clipboard,
83+
shell: &mut Shell<'_, Message>,
84+
) -> event::Status {
85+
self.content.as_widget_mut().on_event(
86+
&mut tree.children[0],
87+
event,
88+
layout,
89+
cursor_position,
90+
renderer,
91+
clipboard,
92+
shell,
93+
)
94+
}
95+
96+
fn mouse_interaction(
97+
&self,
98+
tree: &Tree,
99+
layout: Layout<'_>,
100+
cursor_position: Point,
101+
viewport: &Rectangle,
102+
renderer: &Renderer,
103+
) -> mouse::Interaction {
104+
self.content.as_widget().mouse_interaction(
105+
&tree.children[0],
106+
layout,
107+
cursor_position,
108+
viewport,
109+
renderer,
110+
)
111+
}
112+
113+
fn draw(
114+
&self,
115+
tree: &Tree,
116+
renderer: &mut Renderer,
117+
theme: &Renderer::Theme,
118+
renderer_style: &renderer::Style,
119+
layout: Layout<'_>,
120+
cursor_position: Point,
121+
viewport: &Rectangle,
122+
) {
123+
self.content.as_widget().draw(
124+
&tree.children[0],
125+
renderer,
126+
theme,
127+
renderer_style,
128+
layout,
129+
cursor_position,
130+
viewport,
131+
)
132+
}
133+
134+
fn overlay<'b>(
135+
&'b self,
136+
tree: &'b mut Tree,
137+
layout: Layout<'_>,
138+
_renderer: &Renderer,
139+
) -> Option<overlay::Element<'b, Message, Renderer>> {
140+
// Set position to center of bottom edge
141+
let bounds = layout.bounds();
142+
let position = Point::new(bounds.x + bounds.width / 2.0, bounds.y + bounds.height);
143+
144+
// XXX needed to use RefCell to get &mut for popup element
145+
Some(overlay::Element::new(
146+
position,
147+
Box::new(Overlay {
148+
tree: &mut tree.children[1],
149+
content: &self.popup,
150+
}),
151+
))
152+
}
153+
}
154+
155+
impl<'a, Message, Renderer> From<Popover<'a, Message, Renderer>> for Element<'a, Message, Renderer>
156+
where
157+
Message: 'static,
158+
Renderer: iced_native::Renderer + 'static,
159+
Renderer::Theme: StyleSheet,
160+
{
161+
fn from(popover: Popover<'a, Message, Renderer>) -> Self {
162+
Self::new(popover)
163+
}
164+
}
165+
166+
struct Overlay<'a, 'b, Message, Renderer> {
167+
tree: &'a mut Tree,
168+
content: &'a RefCell<Element<'b, Message, Renderer>>,
169+
}
170+
171+
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
172+
for Overlay<'a, 'b, Message, Renderer>
173+
where
174+
Renderer: iced_native::Renderer,
175+
{
176+
fn layout(&self, renderer: &Renderer, bounds: Size, mut position: Point) -> layout::Node {
177+
// Position is set to the center bottom of the lower widget
178+
179+
let limits = layout::Limits::new(Size::UNIT, bounds);
180+
let mut node = self.content.borrow().as_widget().layout(renderer, &limits);
181+
182+
let width = node.size().width;
183+
position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width);
184+
node.move_to(position);
185+
186+
node
187+
}
188+
189+
fn operate(&mut self, layout: Layout<'_>, operation: &mut dyn Operation<Message>) {
190+
self.content
191+
.borrow()
192+
.as_widget()
193+
.operate(self.tree, layout, operation)
194+
}
195+
196+
fn on_event(
197+
&mut self,
198+
event: Event,
199+
layout: Layout<'_>,
200+
cursor_position: Point,
201+
renderer: &Renderer,
202+
clipboard: &mut dyn Clipboard,
203+
shell: &mut Shell<'_, Message>,
204+
) -> event::Status {
205+
self.content.borrow_mut().as_widget_mut().on_event(
206+
self.tree,
207+
event,
208+
layout,
209+
cursor_position,
210+
renderer,
211+
clipboard,
212+
shell,
213+
)
214+
}
215+
216+
fn mouse_interaction(
217+
&self,
218+
layout: Layout<'_>,
219+
cursor_position: Point,
220+
viewport: &Rectangle,
221+
renderer: &Renderer,
222+
) -> mouse::Interaction {
223+
self.content.borrow().as_widget().mouse_interaction(
224+
self.tree,
225+
layout,
226+
cursor_position,
227+
viewport,
228+
renderer,
229+
)
230+
}
231+
232+
fn draw(
233+
&self,
234+
renderer: &mut Renderer,
235+
theme: &Renderer::Theme,
236+
style: &renderer::Style,
237+
layout: Layout<'_>,
238+
cursor_position: Point,
239+
) {
240+
let bounds = layout.bounds();
241+
self.content.borrow().as_widget().draw(
242+
self.tree,
243+
renderer,
244+
theme,
245+
style,
246+
layout,
247+
cursor_position,
248+
&bounds,
249+
)
250+
}
251+
}

0 commit comments

Comments
 (0)