use crate::docking::panels::LeafId;
use crate::types::WidgetId;
use super::handles::{
ModalHandle, DropdownHandle,
ToolbarHandle, ContextMenuHandle,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DispatchEvent {
Unhandled(WidgetId),
ModalCloseRequested(ModalHandle),
ModalTabClicked { modal: ModalHandle, index: usize },
ModalWizardNext(ModalHandle),
ModalWizardBack(ModalHandle),
DropdownItemClicked { dropdown: DropdownHandle, item_id: String },
ToolbarItemClicked { toolbar: ToolbarHandle, item_id: String },
ChromeTabClicked { tab_index: usize },
ChromeTabClosed { tab_index: usize },
ChromeNewTab,
ChromeWindowControl { control: ChromeWindowControl },
ContextMenuItemClicked { menu: ContextMenuHandle, item_index: usize },
ScrollbarTrackClicked { track_id: WidgetId },
ScrollbarThumbDragStarted { thumb_id: WidgetId },
ChevronStepRequested {
chevron_id: WidgetId,
direction: super::ChevronStepDirection,
},
ResizeHandleDragStarted {
host_id: WidgetId,
edge: ResizeEdge,
},
DropdownSubmenuToggle {
dropdown: DropdownHandle,
trigger_id: String,
},
StickyChevronClicked { host_id: WidgetId },
StickyChevronAtSlotClicked { host_id: WidgetId, slot: String },
DockSeparatorDragStarted { sep_idx: usize },
DockLeafClicked { leaf_id: LeafId },
DockLeafClosedByIndex { leaf_idx: usize },
Indexed { base: String, n: usize },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResizeEdge {
N,
S,
W,
E,
NW,
NE,
SW,
SE,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChromeWindowControl {
Minimize,
MaximizeRestore,
CloseApp,
CloseWindow,
NewWindow,
Menu,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChevronStepDirection {
Up,
Down,
Left,
Right,
}
#[derive(Clone)]
pub enum EventBuilder {
ModalClose { handle: ModalHandle },
ModalTabFromSuffix { handle: ModalHandle },
ModalWizardNext { handle: ModalHandle },
ModalWizardBack { handle: ModalHandle },
DropdownItem { handle: DropdownHandle },
ToolbarItem { handle: ToolbarHandle },
ChromeTabFromSuffix,
ChromeTabCloseFromSuffix,
ChromeNewTab,
ChromeControl(super::ChromeWindowControl),
ContextMenuItem { handle: ContextMenuHandle },
ScrollbarTrack { track_id: WidgetId },
ScrollbarThumb { thumb_id: WidgetId },
ChevronStep { chevron_id: WidgetId, direction: super::ChevronStepDirection },
ResizeHandle { host_id: WidgetId, edge: super::ResizeEdge },
DropdownSubmenuToggleFromSuffix { handle: DropdownHandle },
StickyChevron { host_id: WidgetId },
StickyChevronWithSlot { host_id: WidgetId },
DockSeparatorFromSuffix,
DockLeafFromSuffix,
DockLeafCloseFromSuffix,
IndexedFromSuffix { base: String },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Match {
Exact,
Prefix,
}
#[derive(Clone)]
struct Entry {
pattern: String,
kind: Match,
builder: EventBuilder,
}
#[derive(Default, Clone)]
pub struct ClickDispatcher {
entries: Vec<Entry>,
}
impl ClickDispatcher {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn on_exact(&mut self, pattern: impl Into<String>, builder: EventBuilder) {
self.entries.push(Entry {
pattern: pattern.into(),
kind: Match::Exact,
builder,
});
}
pub fn on_prefix(&mut self, prefix: impl Into<String>, builder: EventBuilder) {
self.entries.push(Entry {
pattern: prefix.into(),
kind: Match::Prefix,
builder,
});
}
pub fn dispatch(&self, clicked: &WidgetId) -> Option<DispatchEvent> {
let id = clicked.0.as_str();
for entry in &self.entries {
if entry.kind == Match::Exact && entry.pattern == id {
return Some(build(&entry.builder, id, &entry.pattern));
}
}
for entry in &self.entries {
if entry.kind == Match::Prefix && id.starts_with(&entry.pattern) {
return Some(build(&entry.builder, id, &entry.pattern));
}
}
None
}
}
fn build(builder: &EventBuilder, id: &str, pattern: &str) -> DispatchEvent {
let suffix = || id.strip_prefix(pattern).unwrap_or("").to_owned();
let unhandled = || DispatchEvent::Unhandled(WidgetId(id.to_owned()));
match builder {
EventBuilder::ModalClose { handle } => {
DispatchEvent::ModalCloseRequested(handle.clone())
}
EventBuilder::ModalTabFromSuffix { handle } => {
match suffix().parse::<usize>() {
Ok(index) => DispatchEvent::ModalTabClicked { modal: handle.clone(), index },
Err(_) => unhandled(),
}
}
EventBuilder::ModalWizardNext { handle } => {
DispatchEvent::ModalWizardNext(handle.clone())
}
EventBuilder::ModalWizardBack { handle } => {
DispatchEvent::ModalWizardBack(handle.clone())
}
EventBuilder::DropdownItem { handle } => {
DispatchEvent::DropdownItemClicked {
dropdown: handle.clone(),
item_id: suffix(),
}
}
EventBuilder::ToolbarItem { handle } => {
DispatchEvent::ToolbarItemClicked {
toolbar: handle.clone(),
item_id: suffix(),
}
}
EventBuilder::ChromeTabFromSuffix => {
match suffix().parse::<usize>() {
Ok(tab_index) => DispatchEvent::ChromeTabClicked { tab_index },
Err(_) => unhandled(),
}
}
EventBuilder::ChromeTabCloseFromSuffix => {
match suffix().parse::<usize>() {
Ok(tab_index) => DispatchEvent::ChromeTabClosed { tab_index },
Err(_) => unhandled(),
}
}
EventBuilder::ChromeNewTab => DispatchEvent::ChromeNewTab,
EventBuilder::ChromeControl(control) => {
DispatchEvent::ChromeWindowControl { control: *control }
}
EventBuilder::ContextMenuItem { handle } => {
match suffix().parse::<usize>() {
Ok(item_index) => DispatchEvent::ContextMenuItemClicked {
menu: handle.clone(),
item_index,
},
Err(_) => unhandled(),
}
}
EventBuilder::ScrollbarTrack { track_id } => {
DispatchEvent::ScrollbarTrackClicked { track_id: track_id.clone() }
}
EventBuilder::ScrollbarThumb { thumb_id } => {
DispatchEvent::ScrollbarThumbDragStarted { thumb_id: thumb_id.clone() }
}
EventBuilder::ChevronStep { chevron_id, direction } => {
DispatchEvent::ChevronStepRequested {
chevron_id: chevron_id.clone(),
direction: *direction,
}
}
EventBuilder::ResizeHandle { host_id, edge } => {
DispatchEvent::ResizeHandleDragStarted {
host_id: host_id.clone(),
edge: *edge,
}
}
EventBuilder::DropdownSubmenuToggleFromSuffix { handle } => {
DispatchEvent::DropdownSubmenuToggle {
dropdown: handle.clone(),
trigger_id: suffix(),
}
}
EventBuilder::StickyChevron { host_id } => {
DispatchEvent::StickyChevronClicked { host_id: host_id.clone() }
}
EventBuilder::StickyChevronWithSlot { host_id } => {
let slot = suffix();
DispatchEvent::StickyChevronAtSlotClicked { host_id: host_id.clone(), slot }
}
EventBuilder::DockSeparatorFromSuffix => {
match suffix().parse::<usize>() {
Ok(sep_idx) => DispatchEvent::DockSeparatorDragStarted { sep_idx },
Err(_) => unhandled(),
}
}
EventBuilder::DockLeafFromSuffix => {
match suffix().parse::<u64>() {
Ok(n) => DispatchEvent::DockLeafClicked { leaf_id: LeafId(n) },
Err(_) => unhandled(),
}
}
EventBuilder::DockLeafCloseFromSuffix => {
match suffix().parse::<usize>() {
Ok(leaf_idx) => DispatchEvent::DockLeafClosedByIndex { leaf_idx },
Err(_) => unhandled(),
}
}
EventBuilder::IndexedFromSuffix { base } => {
match suffix().parse::<usize>() {
Ok(n) => DispatchEvent::Indexed { base: base.clone(), n },
Err(_) => unhandled(),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::handles::{DropdownHandle, ModalHandle};
fn modal_h(id: &str) -> ModalHandle {
ModalHandle { id: WidgetId(id.to_owned()) }
}
fn dropdown_h(id: &str) -> DropdownHandle {
DropdownHandle { id: WidgetId(id.to_owned()) }
}
#[test]
fn exact_beats_prefix_regardless_of_order() {
let mut d = ClickDispatcher::new();
d.on_prefix(
"m:",
EventBuilder::DropdownItem { handle: dropdown_h("m") },
);
d.on_exact(
"m:close",
EventBuilder::ModalClose { handle: modal_h("m") },
);
let ev = d.dispatch(&WidgetId(String::from("m:close"))).unwrap();
assert_eq!(ev, DispatchEvent::ModalCloseRequested(modal_h("m")));
}
#[test]
fn prefix_passes_suffix() {
let mut d = ClickDispatcher::new();
d.on_prefix(
"dd:item:",
EventBuilder::DropdownItem { handle: dropdown_h("dd") },
);
let ev = d.dispatch(&WidgetId(String::from("dd:item:save"))).unwrap();
assert_eq!(
ev,
DispatchEvent::DropdownItemClicked {
dropdown: dropdown_h("dd"),
item_id: "save".to_string(),
},
);
}
#[test]
fn miss_returns_none() {
let d = ClickDispatcher::new();
assert_eq!(d.dispatch(&WidgetId(String::from("nope"))), None);
}
#[test]
fn parse_error_falls_to_unhandled() {
let mut d = ClickDispatcher::new();
d.on_prefix(
"m:tab:",
EventBuilder::ModalTabFromSuffix { handle: modal_h("m") },
);
let ev = d.dispatch(&WidgetId(String::from("m:tab:notanumber"))).unwrap();
assert_eq!(ev, DispatchEvent::Unhandled(WidgetId(String::from("m:tab:notanumber"))));
}
}