pub use super::render::register_input_coordinator_dropdown;
use super::render::{measure_flat, register_context_manager_dropdown};
use super::settings::DropdownSettings;
use super::state::DropdownState;
use super::types::{DropdownItem, DropdownRenderKind, DropdownView, DropdownViewKind, SubmenuWidth};
use crate::docking::panels::DockPanel;
use crate::input::core::coordinator::LayerId;
use crate::input::{Sense, WidgetKind};
use crate::layout::{CompositeKind, CompositeRegistration, DismissFrame, DispatchEvent, DropdownHandle, DropdownNode, EventBuilder, LayoutManager, LayoutNodeId, OverlayEntry, OverlayKind, WidgetNode};
use crate::render::RenderContext;
use crate::types::{OverflowMode, Rect, SizeMode, WidgetId};
pub struct ConsumeEventCtx {
pub cursor: (f64, f64),
pub frame_rect: Rect,
pub viewport: (f64, f64),
}
pub fn consume_event(
event: DispatchEvent,
state: &mut DropdownState,
host_id: &WidgetId,
_ctx: ConsumeEventCtx,
) -> Option<DispatchEvent> {
match event {
DispatchEvent::DropdownSubmenuToggle { ref dropdown, ref trigger_id } => {
if dropdown.id == *host_id {
if state.submenu_open.as_deref() == Some(trigger_id.as_str()) {
state.submenu_open = None;
} else {
state.submenu_open = Some(trigger_id.clone());
}
None
} else {
Some(event)
}
}
_ => Some(event),
}
}
pub fn register_layout_manager_dropdown<P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
parent: LayoutNodeId,
slot_id: &str,
handle: &DropdownHandle,
overlay_rect: Rect,
anchor: Option<Rect>,
view: &mut DropdownView<'_>,
settings: &DropdownSettings,
kind: DropdownRenderKind,
) -> Option<DropdownNode> {
let id: WidgetId = handle.id.clone();
let mut state = layout.dropdowns.remove(&id).unwrap_or_default();
layout.push_overlay(OverlayEntry {
id: slot_id.to_string(),
kind: OverlayKind::Dropdown,
rect: overlay_rect,
anchor,
});
let rect = overlay_rect;
let layer = LayerId::new("dropdown");
let z_order = layout.z_layers().dropdown as u32;
layout.push_dismiss_frame(DismissFrame {
z: z_order,
rect,
overlay_id: WidgetId(slot_id.to_owned()),
});
layout.ctx_mut().input.push_layer(layer.clone(), z_order, true);
let node_id = layout.tree_mut().add_widget(parent, WidgetNode { id: id.clone(), kind: WidgetKind::Dropdown, rect, sense: Sense::CLICK });
layout.dispatcher_mut().on_prefix(
format!("{}:item:", id.0),
EventBuilder::DropdownItem { handle: handle.clone() },
);
layout.dispatcher_mut().on_prefix(
format!("{}:sub-item:", id.0),
EventBuilder::DropdownItem { handle: handle.clone() },
);
layout.dispatcher_mut().on_prefix(
format!("{}:chev:submenu:", id.0),
EventBuilder::DropdownSubmenuToggleFromSuffix { handle: handle.clone() },
);
state.sync_flat_hover_from_layout(layout, &id.0);
{
let main_prefix = format!("{}:item:", id.0);
let submenu_prefix = format!("{}:submenu:", id.0);
let chev_prefix = format!("{}:chev:submenu:", id.0);
let sub_prefix = format!("{}:sub-item:", id.0);
let hovered = layout.hovered_widget().map(|w| w.0.clone());
match hovered {
Some(h) if h.starts_with(&submenu_prefix) && !h.starts_with(&chev_prefix) => {
let rest = &h[submenu_prefix.len()..];
state.submenu_open = Some(rest.to_string());
}
Some(h) if h.starts_with(&chev_prefix) => {
}
Some(h) if h.starts_with(&sub_prefix) => {
}
Some(h) if h.starts_with(&main_prefix) => {
state.submenu_open = None;
}
_ => {}
}
}
register_context_manager_dropdown(
layout.ctx_mut(), render, id.clone(), rect, &mut state, view, settings, kind, &layer,
);
layout.push_composite_registration(CompositeRegistration {
kind: CompositeKind::Dropdown,
slot_id: slot_id.to_string(),
widget_id: id.clone(),
frame_rect: rect,
});
layout.dropdowns.insert(id, state);
Some(DropdownNode(node_id))
}
pub fn open_dropdown_flat<'items, P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
parent: LayoutNodeId,
slot_id: &str,
handle: &DropdownHandle,
items: &'items [DropdownItem<'items>],
settings: &DropdownSettings,
) -> Option<DropdownNode> {
let widget_id = &handle.id;
let (open, hovered_id, origin, anchor_rect, position_override) = {
let st = layout.dropdowns.get(widget_id).map(|s| (
s.open,
s.hovered_id.clone(),
s.effective_origin(),
s.anchor_rect,
s.open_position_override,
));
match st {
Some(v) => v,
None => return None, }
};
if !open {
return None;
}
let (w, h) = measure_flat(items, settings);
let mut view = DropdownView {
anchor: anchor_rect,
position_override,
open: true,
kind: DropdownViewKind::Flat {
items,
hovered_id: hovered_id.as_deref(),
submenu_items: None,
submenu_hovered_id: None,
},
size_mode: SizeMode::AutoFit,
overflow: OverflowMode::Clip,
submenu_width: SubmenuWidth::Auto,
};
register_layout_manager_dropdown(
layout, render, parent,
slot_id, handle,
Rect::new(origin.0, origin.1, w, h),
None,
&mut view, settings,
DropdownRenderKind::Flat,
)
}
pub fn handle_dropdown_dismiss(
state: &DropdownState,
click_pos: (f64, f64),
main_rect: Rect,
submenu_rect: Option<Rect>,
) -> bool {
if !state.open {
return false;
}
let inside_main = main_rect.contains(click_pos.0, click_pos.1);
let inside_sub = submenu_rect
.map(|r| r.contains(click_pos.0, click_pos.1))
.unwrap_or(false);
!inside_main && !inside_sub
}
pub fn handle_dropdown_keyboard(
state: &mut DropdownState,
key: DropdownKey,
items: &[Option<&str>],
) -> DropdownKeyResult {
match key {
DropdownKey::Esc => {
state.close();
DropdownKeyResult::Close
}
DropdownKey::Enter => {
if let Some(ref id) = state.hovered_id {
DropdownKeyResult::Activate(id.clone())
} else {
DropdownKeyResult::None
}
}
DropdownKey::ArrowDown => {
let navigable: Vec<&str> = items.iter().filter_map(|o| *o).collect();
if navigable.is_empty() {
return DropdownKeyResult::None;
}
let next = match &state.hovered_id {
None => navigable[0].to_owned(),
Some(cur) => {
let pos = navigable.iter().position(|&s| s == cur.as_str());
let next_idx = pos.map(|i| (i + 1).min(navigable.len().saturating_sub(1))).unwrap_or(0);
navigable[next_idx].to_owned()
}
};
state.hovered_id = Some(next.clone());
DropdownKeyResult::Hovered(next)
}
DropdownKey::ArrowUp => {
let navigable: Vec<&str> = items.iter().filter_map(|o| *o).collect();
if navigable.is_empty() {
return DropdownKeyResult::None;
}
let next = match &state.hovered_id {
None => navigable[navigable.len().saturating_sub(1)].to_owned(),
Some(cur) => {
let pos = navigable.iter().position(|&s| s == cur.as_str());
let next_idx = pos.map(|i| i.saturating_sub(1)).unwrap_or(0);
navigable[next_idx].to_owned()
}
};
state.hovered_id = Some(next.clone());
DropdownKeyResult::Hovered(next)
}
DropdownKey::Tab => {
state.close();
DropdownKeyResult::Close
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DropdownKey {
ArrowDown,
ArrowUp,
Enter,
Esc,
Tab,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DropdownKeyResult {
Close,
Activate(String),
Hovered(String),
None,
}