pub use super::render::register_input_coordinator_modal;
use super::render::register_context_manager_modal;
use super::settings::ModalSettings;
use super::state::ModalState;
use super::types::{ModalRenderKind, ModalView};
use crate::input::text::store::TextFieldConfig;
use crate::docking::panels::DockPanel;
use crate::input::core::coordinator::LayerId;
use crate::types::CompositeId;
use crate::input::{Sense, WidgetKind};
use crate::layout::{CompositeKind, CompositeRegistration, DismissFrame, DispatchEvent, EventBuilder, LayoutManager, LayoutNodeId, ModalHandle, ModalNode, OverlayEntry, OverlayKind, WidgetNode};
use crate::render::RenderContext;
use crate::types::{Rect, WidgetId};
pub fn modal_body_hovered_widget<'a, P: DockPanel>(
layout: &'a LayoutManager<P>,
body_rect: Rect,
) -> Option<&'a WidgetId> {
let (mx, my) = layout.ctx().input.pointer_pos()?;
if !body_rect.contains(mx, my) {
return None;
}
layout.ctx().input.hovered_widget()
}
pub struct ConsumeEventCtx {
pub cursor: (f64, f64),
pub frame_rect: Rect,
pub viewport: (f64, f64),
}
pub fn consume_event(
event: DispatchEvent,
state: &mut ModalState,
host_id: &WidgetId,
ctx: ConsumeEventCtx,
) -> Option<DispatchEvent> {
match event {
DispatchEvent::ChevronStepRequested { ref chevron_id, direction } => {
let is_own = chevron_id.0 == format!("{}:chevron_up", host_id.0)
|| chevron_id.0 == format!("{}:chevron_down", host_id.0)
|| chevron_id.0 == format!("{}:chevron_left", host_id.0)
|| chevron_id.0 == format!("{}:chevron_right", host_id.0);
if is_own {
state.body_chevron_step(direction);
None
} else {
Some(event)
}
}
DispatchEvent::ResizeHandleDragStarted { host_id: ref hid, edge } => {
if hid == host_id {
let min = (200.0_f64, 120.0_f64);
let cap = (f64::INFINITY, f64::INFINITY);
state.start_resize(edge, ctx.frame_rect, ctx.cursor, min, cap);
None
} else {
Some(event)
}
}
DispatchEvent::ScrollbarTrackClicked { ref track_id } => {
if track_id.0 == format!("{}:scrollbar_track", host_id.0) {
state.body_scroll_track_click(ctx.cursor.1);
None
} else {
Some(event)
}
}
DispatchEvent::ScrollbarThumbDragStarted { ref thumb_id } => {
if thumb_id.0 == format!("{}:scrollbar_handle", host_id.0) {
state.start_body_scroll_drag(ctx.cursor.1);
None
} else {
Some(event)
}
}
_ => Some(event),
}
}
pub fn register_layout_manager_modal<P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
parent: LayoutNodeId,
slot_id: &str,
handle: &ModalHandle,
overlay_rect: Rect,
anchor: Option<Rect>,
view: &mut ModalView<'_>,
settings: &ModalSettings,
kind: &ModalRenderKind,
) -> Option<ModalNode> {
let id: WidgetId = handle.id.clone();
let mut state = layout.modals.remove(&id).unwrap_or_default();
layout.push_overlay(OverlayEntry {
id: slot_id.to_string(),
kind: OverlayKind::Modal,
rect: overlay_rect,
anchor,
});
let rect = overlay_rect;
let layer = LayerId::modal();
let z_order = layout.z_layers().modal 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::Modal, rect, sense: Sense::CLICK });
let dispatcher = layout.dispatcher_mut();
dispatcher.on_exact(
format!("{}:close", id.0),
EventBuilder::ModalClose { handle: handle.clone() },
);
dispatcher.on_prefix(
format!("{}:footer:", id.0),
EventBuilder::ModalClose { handle: handle.clone() },
);
dispatcher.on_prefix(
format!("{}:tab:", id.0),
EventBuilder::ModalTabFromSuffix { handle: handle.clone() },
);
dispatcher.on_exact(
format!("{}:wizard:next", id.0),
EventBuilder::ModalWizardNext { handle: handle.clone() },
);
dispatcher.on_exact(
format!("{}:wizard:back", id.0),
EventBuilder::ModalWizardBack { handle: handle.clone() },
);
if matches!(view.overflow, crate::types::OverflowMode::Scrollbar) {
dispatcher.on_exact(
format!("{}:scrollbar_track", id.0),
EventBuilder::ScrollbarTrack { track_id: WidgetId::new(format!("{}:scrollbar_track", id.0)) },
);
dispatcher.on_exact(
format!("{}:scrollbar_handle", id.0),
EventBuilder::ScrollbarThumb { thumb_id: WidgetId::new(format!("{}:scrollbar_handle", id.0)) },
);
}
if matches!(view.overflow, crate::types::OverflowMode::Chevrons) {
use crate::layout::ChevronStepDirection;
for (suffix, dir) in [
("chevron_up", ChevronStepDirection::Up),
("chevron_down", ChevronStepDirection::Down),
("chevron_left", ChevronStepDirection::Left),
("chevron_right", ChevronStepDirection::Right),
] {
dispatcher.on_exact(
format!("{}:{}", id.0, suffix),
EventBuilder::ChevronStep {
chevron_id: WidgetId::new(format!("{}:{}", id.0, suffix)),
direction: dir,
},
);
}
}
if view.resizable {
use crate::layout::ResizeEdge;
for (suffix, edge) in &[
("resize_n", ResizeEdge::N),
("resize_s", ResizeEdge::S),
("resize_w", ResizeEdge::W),
("resize_e", ResizeEdge::E),
("resize_nw", ResizeEdge::NW),
("resize_ne", ResizeEdge::NE),
("resize_sw", ResizeEdge::SW),
("resize_se", ResizeEdge::SE),
] {
dispatcher.on_exact(
format!("{}:{}", id.0, suffix),
EventBuilder::ResizeHandle { host_id: id.clone(), edge: *edge },
);
}
}
register_context_manager_modal(
layout.ctx_mut(), render, id.clone(), rect, &mut state, view, settings, kind, &layer,
);
layout.push_composite_registration(CompositeRegistration {
kind: CompositeKind::Modal,
slot_id: slot_id.to_string(),
widget_id: id.clone(),
frame_rect: rect,
});
layout.modals.insert(id, state);
Some(ModalNode(node_id))
}
pub fn drag_outcome_modal(state: &ModalState) -> Option<crate::layout::DragOutcome> {
if state.scroll.is_dragging {
return Some(crate::layout::DragOutcome::ModalBodyScroll);
}
if state.resize_drag.is_some() {
return Some(crate::layout::DragOutcome::ModalResize);
}
None
}
pub fn handle_modal_drag(
state: &mut ModalState,
cursor_pos: (f64, f64),
screen_size: (f64, f64),
modal_size: (f64, f64),
) {
state.update_drag(cursor_pos, screen_size, modal_size);
}
pub fn register_modal_text_fields<P: DockPanel>(
layout: &mut LayoutManager<P>,
body_rect: Rect,
fields: &[(&str, Rect, TextFieldConfig)],
) {
let coord = &mut layout.ctx_mut().input;
for (id, local_rect, config) in fields {
let screen_rect = Rect::new(
body_rect.x + local_rect.x,
body_rect.y + local_rect.y,
local_rect.width,
local_rect.height,
);
coord.register_text_field(*id, screen_rect, config.clone());
}
}
pub fn register_modal_button<P: DockPanel>(
layout: &mut LayoutManager<P>,
host_id: impl Into<WidgetId>,
child_id: impl Into<WidgetId>,
rect: Rect,
sense: Sense,
layer: &LayerId,
) -> CompositeId {
let coord = &mut layout.ctx_mut().input;
let host = coord.register_composite(host_id, WidgetKind::Panel, rect, Sense::NONE, layer);
coord.register_child(&host, child_id, WidgetKind::Button, rect, sense);
host
}
pub fn modal_body_finish<P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
frame_rect: Rect,
state: &mut ModalState,
view: &ModalView<'_>,
settings: &ModalSettings,
kind: &ModalRenderKind,
) {
use super::render::{draw_body_overflow_chevrons, register_body_overflow};
draw_body_overflow_chevrons(render, frame_rect, state, view, settings, kind);
let modal_id = CompositeId(WidgetId::new("modal-widget"));
register_body_overflow(
&mut layout.ctx_mut().input,
&modal_id,
frame_rect,
view,
settings,
kind,
state,
);
}
pub fn modal_body_scope<P: DockPanel, F>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
frame_rect: Rect,
state: &mut ModalState,
view: &ModalView<'_>,
settings: &ModalSettings,
kind: &ModalRenderKind,
body_fn: F,
) where
F: FnOnce(&mut dyn RenderContext, Rect),
{
use super::render::{body_rect as modal_body_rect, draw_body_overflow_chevrons, register_body_overflow};
let br = modal_body_rect(frame_rect, view, settings, kind);
body_fn(render, br);
draw_body_overflow_chevrons(render, frame_rect, state, view, settings, kind);
let modal_id = CompositeId(WidgetId::new("modal-widget"));
register_body_overflow(
&mut layout.ctx_mut().input,
&modal_id,
frame_rect,
view,
settings,
kind,
state,
);
}
pub fn modal_header_hit(
modal_rect: Rect,
px: f64,
py: f64,
header_height: f64,
close_btn_width: f64,
) -> bool {
px >= modal_rect.x
&& px <= modal_rect.x + modal_rect.width - close_btn_width
&& py >= modal_rect.y
&& py <= modal_rect.y + header_height
}