use iced_core::{
Padding, Rectangle, Shell, Size,
layout::{Layout, Node},
mouse, renderer,
widget::Tree,
};
use super::menu_bar::{GlobalState, MenuBarTask};
use super::menu_tree::{Item, MenuState};
use crate::style::menu_bar::Catalog;
#[derive(Debug, Clone, Copy)]
pub enum DrawPath {
FakeHovering,
Backdrop,
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Direction {
Positive,
Negative,
}
impl Direction {
pub(super) fn flip(self) -> Self {
match self {
Self::Positive => Self::Negative,
Self::Negative => Self::Positive,
}
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub(super) enum Axis {
Horizontal,
Vertical,
}
pub(super) type Index = Option<usize>;
#[derive(Debug, Clone, Copy)]
pub(super) enum RecEvent {
Event,
Close,
None,
}
#[derive(Debug, Clone, Copy)]
pub struct ScrollSpeed {
pub line: f32,
pub pixel: f32,
}
pub fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
Rectangle {
x: rect.x - padding.left,
y: rect.y - padding.top,
width: rect.width + padding.x(),
height: rect.height + padding.y(),
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct MenuSlice {
pub(super) start_index: usize,
pub(super) end_index: usize,
pub(super) lower_bound_rel: f32,
pub(super) upper_bound_rel: f32,
}
impl MenuSlice {
pub(super) fn from_bounds_rel(
lower_bound_rel: f32,
upper_bound_rel: f32,
items_node: &Node,
get_position: fn(&Node) -> f32,
) -> Self {
let max_index = items_node.children().len().saturating_sub(1);
let nodes = items_node.children();
let start_index = search_bound(0, max_index, lower_bound_rel, nodes, get_position);
let end_index = search_bound(start_index, max_index, upper_bound_rel, nodes, get_position);
Self {
start_index,
end_index,
lower_bound_rel,
upper_bound_rel,
}
}
}
pub(super) fn search_bound(
default_left: usize,
default_right: usize,
bound: f32,
list: &[Node],
get_position: fn(&Node) -> f32,
) -> usize {
let mut left = default_left;
let mut right = default_right;
while left != right {
let m = usize::midpoint(left, right) + 1;
if get_position(&list[m]) > bound {
right = m - 1;
} else {
left = m;
}
}
left
}
pub(super) fn clip_node_y(node: &Node, height: f32, offset: f32) -> Node {
let node_bounds = node.bounds();
Node::with_children(
Size::new(node_bounds.width, height),
node.children()
.iter()
.map(|n| n.clone().translate([0.0, -offset]))
.collect(),
)
.move_to(node_bounds.position())
.translate([0.0, offset])
}
pub(super) fn clip_node_x(node: &Node, width: f32, offset: f32) -> Node {
let node_bounds = node.bounds();
Node::with_children(
Size::new(width, node_bounds.height),
node.children()
.iter()
.map(|n| n.clone().translate([-offset, 0.0]))
.collect(),
)
.move_to(node_bounds.position())
.translate([offset, 0.0])
}
pub(super) struct GlobalParameters<'a, Theme: crate::style::menu_bar::Catalog> {
pub(super) safe_bounds_margin: f32,
pub(super) draw_path: DrawPath,
pub(super) scroll_speed: ScrollSpeed,
pub(super) close_on_item_click: bool,
pub(super) close_on_background_click: bool,
pub(super) class: Theme::Class<'a>,
}
pub(super) fn try_open_menu<'a, 'b, Message, Theme: Catalog, Renderer: renderer::Renderer>(
items: &mut [Item<'a, Message, Theme, Renderer>],
menu_state: &mut MenuState,
item_trees: &mut [Tree],
item_layouts: impl Iterator<Item = Layout<'b>>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
) {
let old_active = menu_state.active;
let slice = menu_state.slice;
for (i, ((item, tree), layout)) in
itl_iter_slice_enum!(slice, items;iter_mut, item_trees;iter_mut, item_layouts)
{
if cursor.is_over(layout.bounds()) {
if item.menu.is_some() {
menu_state.open_new_menu(i, item, tree);
}
break;
}
}
if menu_state.active != old_active {
shell.invalidate_layout();
shell.request_redraw();
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn schedule_close_on_click<
'a,
'b,
Message,
Theme: Catalog,
Renderer: renderer::Renderer,
>(
global_state: &mut GlobalState,
global_parameters: &GlobalParameters<'_, Theme>,
slice: MenuSlice,
items: &mut [Item<'a, Message, Theme, Renderer>],
slice_layout: impl Iterator<Item = Layout<'b>>,
cursor: mouse::Cursor,
menu_close_on_item_click: Option<bool>,
menu_close_on_background_click: Option<bool>,
) {
global_state.clear_task();
let mut coc_handled = false;
for (item, layout) in items[slice.start_index..=slice.end_index] .iter_mut()
.zip(slice_layout)
{
if cursor.is_over(layout.bounds()) {
if let Some(coc) = item.close_on_click {
coc_handled = true;
if coc {
global_state.schedule(MenuBarTask::CloseOnClick);
}
}
for cocfb in [
menu_close_on_item_click,
Some(global_parameters.close_on_item_click),
] {
if let (false, Some(coc)) = (coc_handled, cocfb) {
coc_handled = true;
if coc {
global_state.schedule(MenuBarTask::CloseOnClick);
}
}
}
break;
}
}
for cocfb in [
menu_close_on_background_click,
Some(global_parameters.close_on_background_click),
] {
if let (false, Some(coc)) = (coc_handled, cocfb) {
coc_handled = true;
if coc {
global_state.schedule(MenuBarTask::CloseOnClick);
}
}
}
}
macro_rules! itl_iter_slice {
($slice:expr, $items:expr;$iter_0:ident, $item_trees:expr;$iter_1:ident, $slice_layout:expr) => {
$items[$slice.start_index..=$slice.end_index]
.$iter_0()
.zip($item_trees[$slice.start_index..=$slice.end_index].$iter_1())
.zip($slice_layout)
};
}
pub(super) use itl_iter_slice;
macro_rules! itl_iter_slice_enum {
($slice:expr, $items:expr;$iter_0:ident, $item_trees:expr;$iter_1:ident, $slice_layout:expr) => {
itl_iter_slice!($slice, $items;$iter_0, $item_trees;$iter_1, $slice_layout)
.enumerate()
.map(move |(i, ((item, tree), layout))| (i + $slice.start_index, ((item, tree), layout)))
};
}
pub(super) use itl_iter_slice_enum;
#[derive(Debug, Clone, Copy)]
pub(super) struct SafeTriangle {
pub(super) p1: iced_core::Point,
pub(super) p2: iced_core::Point,
pub(super) p3: iced_core::Point,
}
impl SafeTriangle {
pub(super) fn new(
p1: iced_core::Point,
child_bounds: Rectangle,
direction: (Direction, Direction),
) -> Self {
let (child_corner1, child_corner2) = match direction.0 {
Direction::Positive => (
iced_core::Point::new(child_bounds.x, child_bounds.y),
iced_core::Point::new(child_bounds.x, child_bounds.y + child_bounds.height),
),
Direction::Negative => (
iced_core::Point::new(child_bounds.x + child_bounds.width, child_bounds.y),
iced_core::Point::new(
child_bounds.x + child_bounds.width,
child_bounds.y + child_bounds.height,
),
),
};
Self {
p1,
p2: child_corner1,
p3: child_corner2,
}
}
pub(super) fn contains(&self, point: iced_core::Point) -> bool {
let sign = |p1: iced_core::Point, p2: iced_core::Point, p3: iced_core::Point| -> f32 {
(p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
};
let d1 = sign(point, self.p1, self.p2);
let d2 = sign(point, self.p2, self.p3);
let d3 = sign(point, self.p3, self.p1);
let has_neg = (d1 < 0.0) || (d2 < 0.0) || (d3 < 0.0);
let has_pos = (d1 > 0.0) || (d2 > 0.0) || (d3 > 0.0);
!(has_neg && has_pos)
}
}