use crate::context_menu;
pub use crate::style::{
context_menu::{Catalog, Style},
status::{self, StyleFn},
};
use iced_core::{
Border, Clipboard, Color, Element, Event, Layout, Point, Shell, Size, keyboard,
layout::{Limits, Node},
mouse::{self, Cursor},
overlay, renderer, touch,
widget::Tree,
window,
};
#[allow(missing_debug_implementations)]
pub struct ContextMenuOverlay<
'a,
'b,
Message,
Theme = iced_widget::Theme,
Renderer = iced_widget::Renderer,
> where
Message: 'a + Clone,
Renderer: 'a + renderer::Renderer,
Theme: Catalog,
'b: 'a,
{
position: Point,
tree: &'a mut Tree,
content: &'a mut Element<'b, Message, Theme, Renderer>,
class: &'a Theme::Class<'b>,
state: &'a mut context_menu::State,
}
impl<'a, 'b, Message, Theme, Renderer> ContextMenuOverlay<'a, 'b, Message, Theme, Renderer>
where
Message: Clone,
Renderer: renderer::Renderer,
Theme: 'a + Catalog,
'b: 'a,
{
pub(crate) fn new(
position: Point,
tree: &'a mut Tree,
content: &'a mut Element<'b, Message, Theme, Renderer>,
class: &'a <Theme as Catalog>::Class<'b>,
state: &'a mut context_menu::State,
) -> Self {
ContextMenuOverlay {
position,
tree,
content,
class,
state,
}
}
#[must_use]
pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
overlay::Element::new(Box::new(self))
}
}
impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
for ContextMenuOverlay<'a, 'b, Message, Theme, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + renderer::Renderer,
Theme: 'a + Catalog,
'b: 'a,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
let limits = Limits::new(Size::ZERO, bounds);
let max_size = limits.max();
let mut content = self
.content
.as_widget_mut()
.layout(self.tree, renderer, &limits);
let mut position = self.position;
if position.x + content.size().width > bounds.width {
position.x = f32::max(0.0, position.x - content.size().width);
}
if position.y + content.size().height > bounds.height {
position.y = f32::max(0.0, position.y - content.size().height);
}
content.move_to_mut(position);
Node::with_children(max_size, vec![content])
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: Cursor,
) {
let bounds = layout.bounds();
let style_sheet = theme.style(self.class, status::Status::Active);
let content_layout = layout
.children()
.next()
.expect("widget: Layout should have a content layout.");
renderer.fill_quad(
renderer::Quad {
bounds: content_layout.bounds(),
border: Border {
radius: (0.0).into(),
width: 0.0,
color: Color::TRANSPARENT,
},
..Default::default()
},
style_sheet.background,
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
content_layout,
cursor,
&bounds,
);
}
fn update(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: iced_core::mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) {
let layout_children = layout
.children()
.next()
.expect("widget: Layout should have a content layout.");
let mut forward_event_to_children = true;
let mut capture_event = false;
match &event {
Event::Keyboard(keyboard::Event::KeyPressed { key, .. })
if *key == keyboard::Key::Named(keyboard::key::Named::Escape) =>
{
self.state.show = false;
forward_event_to_children = false;
shell.capture_event();
shell.request_redraw();
}
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left | mouse::Button::Right,
))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(layout_children.bounds()) {
capture_event = true;
} else {
self.state.show = false;
forward_event_to_children = false;
shell.request_redraw();
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
self.state.show = false;
capture_event = true;
shell.request_redraw();
}
Event::Window(window::Event::Resized { .. }) => {
self.state.show = false;
forward_event_to_children = false;
capture_event = true;
shell.request_redraw();
}
_ => {}
}
if forward_event_to_children {
self.content.as_widget_mut().update(
self.tree,
event,
layout_children,
cursor,
renderer,
clipboard,
shell,
&layout.bounds(),
);
}
if capture_event {
shell.capture_event();
}
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation,
) {
let content_layout = layout
.children()
.next()
.expect("widget: Layout should have a content layout.");
self.content
.as_widget_mut()
.operate(self.tree, content_layout, renderer, operation);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
) -> mouse::Interaction {
let bounds = layout.bounds();
self.content.as_widget().mouse_interaction(
self.tree,
layout
.children()
.next()
.expect("widget: Layout should have a content layout."),
cursor,
&bounds,
renderer,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use iced_core::Point;
use iced_widget::text::Text;
#[derive(Clone)]
enum TestMessage {}
fn create_test_tree() -> Tree {
Tree::empty()
}
fn create_test_content()
-> Element<'static, TestMessage, iced_widget::Theme, iced_widget::Renderer> {
Text::new("Test content").into()
}
fn create_test_state() -> crate::context_menu::State {
crate::context_menu::State::new()
}
#[test]
fn context_menu_overlay_new_creates_instance() {
let mut tree = create_test_tree();
let mut content = create_test_content();
let class = &<iced_widget::Theme as Catalog>::default();
let mut state = create_test_state();
let position = Point::new(100.0, 100.0);
let overlay = ContextMenuOverlay::new(position, &mut tree, &mut content, class, &mut state);
assert_eq!(overlay.position, position);
}
#[test]
fn context_menu_overlay_creates_with_different_positions() {
let mut tree1 = create_test_tree();
let mut tree2 = create_test_tree();
let mut tree3 = create_test_tree();
let mut content1 = create_test_content();
let mut content2 = create_test_content();
let mut content3 = create_test_content();
let class = &<iced_widget::Theme as Catalog>::default();
let mut state1 = create_test_state();
let mut state2 = create_test_state();
let mut state3 = create_test_state();
let overlay1 =
ContextMenuOverlay::new(Point::ORIGIN, &mut tree1, &mut content1, class, &mut state1);
let overlay2 = ContextMenuOverlay::new(
Point::new(500.0, 300.0),
&mut tree2,
&mut content2,
class,
&mut state2,
);
let overlay3 = ContextMenuOverlay::new(
Point::new(1000.0, 800.0),
&mut tree3,
&mut content3,
class,
&mut state3,
);
assert_eq!(overlay1.position, Point::ORIGIN);
assert_eq!(overlay2.position, Point::new(500.0, 300.0));
assert_eq!(overlay3.position, Point::new(1000.0, 800.0));
}
#[test]
fn context_menu_overlay_converts_to_overlay_element() {
let mut tree = create_test_tree();
let mut content = create_test_content();
let class = &<iced_widget::Theme as Catalog>::default();
let mut state = create_test_state();
let position = Point::new(100.0, 100.0);
let overlay = ContextMenuOverlay::new(position, &mut tree, &mut content, class, &mut state);
let _overlay_element = overlay.overlay();
}
#[test]
fn context_menu_overlay_state_can_be_shown() {
let mut state = create_test_state();
assert!(!state.show);
state.show = true;
assert!(state.show);
}
#[test]
fn context_menu_overlay_state_can_be_hidden() {
let mut state = create_test_state();
state.show = true;
assert!(state.show);
state.show = false;
assert!(!state.show);
}
#[test]
fn context_menu_overlay_state_tracks_cursor_position() {
let mut state = create_test_state();
assert_eq!(state.cursor_position, Point::ORIGIN);
state.cursor_position = Point::new(123.4, 567.8);
assert_eq!(state.cursor_position.x, 123.4);
assert_eq!(state.cursor_position.y, 567.8);
}
#[test]
fn context_menu_overlay_state_show_and_position_update_together() {
let mut state = create_test_state();
assert!(!state.show);
assert_eq!(state.cursor_position, Point::ORIGIN);
state.show = true;
state.cursor_position = Point::new(200.0, 300.0);
assert!(state.show);
assert_eq!(state.cursor_position, Point::new(200.0, 300.0));
state.show = false;
assert!(!state.show);
assert_eq!(state.cursor_position, Point::new(200.0, 300.0));
}
#[test]
fn context_menu_overlay_position_can_be_negative() {
let mut tree = create_test_tree();
let mut content = create_test_content();
let class = &<iced_widget::Theme as Catalog>::default();
let mut state = create_test_state();
let position = Point::new(-50.0, -100.0);
let overlay = ContextMenuOverlay::new(position, &mut tree, &mut content, class, &mut state);
assert_eq!(overlay.position.x, -50.0);
assert_eq!(overlay.position.y, -100.0);
}
#[test]
fn context_menu_overlay_position_accuracy() {
let mut tree = create_test_tree();
let mut content = create_test_content();
let class = &<iced_widget::Theme as Catalog>::default();
let mut state = create_test_state();
let position = Point::new(123.456, 789.012);
let overlay = ContextMenuOverlay::new(position, &mut tree, &mut content, class, &mut state);
assert_eq!(overlay.position.x, 123.456);
assert_eq!(overlay.position.y, 789.012);
}
}