use bevy_ecs::{
prelude::*,
system::{StaticSystemParam, SystemParam},
};
use bevy_input::prelude::*;
use bevy_math::prelude::*;
use bevy_picking::pointer::{PointerId, PointerInteraction};
use bevy_transform::components::Transform;
use crossflow::*;
use rmf_site_camera::*;
use tracing::warn;
use web_time::Instant;
use crate::*;
const DOUBLE_CLICK_DURATION_MILLISECONDS: u128 = 500;
pub fn process_new_selector(
In(key): In<BufferKey<RunSelector>>,
mut access: BufferAccessMut<RunSelector>,
mut gate: BufferGateAccessMut,
) -> Option<RunSelector> {
let Ok(mut buffer) = access.get_mut(&key) else {
return None;
};
let Ok(mut gate) = gate.get_mut(key) else {
return None;
};
let output = buffer.pull();
if output.is_some() {
gate.close_gate();
}
output
}
pub fn selection_update(
In(BlockingService {
request: Select(new_selection),
..
}): BlockingServiceInput<Select>,
mut selected: Query<&mut Selected>,
mut selection: ResMut<Selection>,
) {
if selection.0 != new_selection.map(|s| s.candidate) {
if let Some(previous_selection) = selection.0 {
if let Ok(mut selected) = selected.get_mut(previous_selection) {
selected.is_selected = false;
}
}
if let Some(new_selection) = new_selection {
if let Ok(mut selected) = selected.get_mut(new_selection.candidate) {
selected.is_selected = true;
}
}
selection.0 = new_selection.map(|s| s.candidate);
}
}
pub fn picking_service<Filter: SystemParam + 'static>(
In(ContinuousService { key }): ContinuousServiceInput<(), ()>,
orders: ContinuousQuery<(), ()>,
mut picks: EventReader<ChangePick>,
mut hover: EventWriter<Hover>,
filter: StaticSystemParam<Filter>,
) where
for<'w, 's> Filter::Item<'w, 's>: SelectionFilter,
{
let Some(orders) = orders.view(&key) else {
return;
};
if orders.is_empty() {
return;
}
let mut filter = filter.into_inner();
if let Some(pick) = picks.read().last() {
hover.write(Hover(
pick.to
.and_then(|change_pick_to| filter.filter_pick(change_pick_to)),
));
}
}
pub fn make_selectable_entities_pickable(
mut commands: Commands,
new_selectables: Query<&Selectable, Added<Selectable>>,
targets: Query<(Option<&Hovered>, Option<&Selected>)>,
) {
for selectable in &new_selectables {
if let Ok((hovered, selected)) = targets.get(selectable.element) {
if hovered.is_none() {
commands
.entity(selectable.element)
.insert(Hovered::default());
}
if selected.is_none() {
commands
.entity(selectable.element)
.insert(Selected::default());
}
}
}
}
pub fn hover_service<Filter: SystemParam + 'static>(
In(ContinuousService { key }): ContinuousServiceInput<(), (), StreamOf<Hover>>,
mut orders: ContinuousQuery<(), (), StreamOf<Hover>>,
mut hovered: Query<&mut Hovered>,
mut hovering: ResMut<Hovering>,
mut hover: EventReader<Hover>,
mouse_button_input: Res<ButtonInput<MouseButton>>,
touch_input: Res<Touches>,
mut select: EventWriter<Select>,
block_status: Res<PickBlockStatus>,
filter: StaticSystemParam<Filter>,
selection_blockers: Res<SelectionBlockers>,
) where
for<'w, 's> Filter::Item<'w, 's>: SelectionFilter,
{
let Some(mut orders) = orders.get_mut(&key) else {
return;
};
if orders.is_empty() {
return;
}
if selection_blockers.blocking() {
return;
}
let mut filter = filter.into_inner();
if let Some(new_hovered) = hover.read().last() {
let new_hovered = new_hovered.0.and_then(|e| filter.filter_select(e));
if hovering.0 != new_hovered {
if let Some(previous_hovered) = hovering.0 {
if let Ok(mut hovering) = hovered.get_mut(previous_hovered) {
hovering.is_hovered = false;
}
}
if let Some(new_hovered) = new_hovered {
if let Ok(mut hovering) = hovered.get_mut(new_hovered) {
hovering.is_hovered = true;
}
}
hovering.0 = new_hovered;
orders.for_each(|order| order.streams().send(Hover(new_hovered)));
}
}
let clicked = mouse_button_input.just_pressed(MouseButton::Left)
|| touch_input.iter_just_pressed().next().is_some();
let blocked = block_status.blocked();
if clicked && !blocked {
if let Some(new_select) = filter.on_click(Hover(hovering.0)) {
select.write(new_select);
}
}
}
pub fn select_service<Filter: SystemParam + 'static>(
In(ContinuousService { key }): ContinuousServiceInput<(), (), StreamOf<Select>>,
mut orders: ContinuousQuery<(), (), StreamOf<Select>>,
mut select: EventReader<Select>,
filter: StaticSystemParam<Filter>,
mut commands: Commands,
) where
for<'w, 's> Filter::Item<'w, 's>: SelectionFilter,
{
let Some(mut orders) = orders.get_mut(&key) else {
return;
};
if orders.is_empty() {
return;
}
let mut filter = filter.into_inner();
for selected in select.read() {
let mut selected = *selected;
if let Some(selected) = &mut selected.0 {
match filter.filter_select(selected.candidate) {
Some(candidate) => selected.candidate = candidate,
None => {
if selected.provisional {
if let Ok(mut entity_mut) = commands.get_entity(selected.candidate) {
entity_mut.despawn();
}
}
continue;
}
}
}
orders.for_each(|order| order.streams().send(selected));
}
}
pub fn refresh_picked(In(_): In<()>, mut picked: ResMut<Picked>) {
picked.refresh = true;
}
pub fn clear_hover_select(
In(_): In<()>,
mut hovered: Query<&mut Hovered>,
mut hovering: ResMut<Hovering>,
mut selected: Query<&mut Selected>,
mut selection: ResMut<Selection>,
) {
if let Some(previous_hovering) = hovering.0.take() {
if let Ok(mut hovered) = hovered.get_mut(previous_hovering) {
hovered.is_hovered = false;
}
}
if let Some(previous_selection) = selection.0.take() {
if let Ok(mut selected) = selected.get_mut(previous_selection) {
selected.is_selected = false;
}
}
}
pub fn inspector_cursor_transform(
In(ContinuousService { key }): ContinuousServiceInput<(), ()>,
orders: ContinuousQuery<(), ()>,
mut cursor: Query<&mut Transform, With<CursorFrame>>,
active_camera: ActiveCameraQuery,
pointers: Query<(&PointerId, &PointerInteraction)>,
) {
let Some(orders) = orders.view(&key) else {
return;
};
if orders.is_empty() {
return;
}
let Some((_, interactions)) = pointers.single().ok() else {
return;
};
let Ok(active_camera) = active_camera_maybe(&active_camera) else {
return;
};
let Some((position, normal)) = interactions
.iter()
.find(|(_, hit_data)| hit_data.camera == active_camera)
.and_then(|(_, hit_data)| {
hit_data
.position
.zip(hit_data.normal.and_then(|n| Dir3::new(n).ok()))
})
else {
return;
};
let mut transform = match cursor.single_mut() {
Ok(trans) => trans,
Err(err) => {
warn!("Could not get cursor transform. Reason: {:#?}", err);
return;
}
};
let ray = Ray3d::new(position, normal);
*transform = Transform::from_matrix(Mat4::from_rotation_translation(
Quat::from_rotation_arc(Vec3::new(0., 0., 1.), *ray.direction),
ray.origin,
));
}
pub fn deselect_on_esc(In(code): In<KeyCode>, mut select: EventWriter<Select>) {
if matches!(code, KeyCode::Escape) {
select.write(Select::new(None));
}
}
pub(crate) fn build_selection_workflow(
default_service: Service<(), ()>,
new_selector_service: Service<(), (), StreamOf<RunSelector>>,
) -> impl FnOnce(Scope<(), ()>, &mut Builder) -> DeliverySettings {
move |scope, builder| {
let process_new_selector_service = builder
.commands()
.spawn_service(process_new_selector.into_blocking_service());
let run_service_buffer = builder.create_buffer::<RunSelector>(BufferSettings::keep_last(1));
let input = scope.input.fork_clone(builder);
let inspector = input.clone_chain(builder).then_node(default_service);
let new_selector_node = input.clone_chain(builder).then_node(new_selector_service);
builder.connect(new_selector_node.output, scope.terminate);
new_selector_node
.streams
.chain(builder)
.connect(run_service_buffer.input_slot());
let open_gate = builder.create_gate_open(run_service_buffer);
let trim = builder.create_trim([TrimBranch::between(open_gate.input, inspector.input)]);
builder.connect(trim.output, open_gate.input);
builder
.listen(run_service_buffer)
.then(process_new_selector_service)
.dispose_on_none()
.connect(trim.input);
open_gate
.output
.chain(builder)
.map_block(|r: RunSelector| (r.input, r.selector.into()))
.then_injection()
.trigger()
.connect(inspector.input);
DeliverySettings::Serial
}
}
pub fn send_double_click_event(
mut select: EventReader<Select>,
mut double_clicked: Local<DoubleClickSelection>,
mut double_click_select: EventWriter<DoubleClickSelect>,
) {
for selected in select.read() {
let current_time = Instant::now();
let Some(selected_entity) = selected.0.map(|c| c.candidate) else {
return;
};
if let Some(last_entity) = double_clicked.last_selected_entity {
let elapsed_time = current_time
.duration_since(double_clicked.last_selected_time)
.as_millis();
if last_entity == selected_entity && elapsed_time < DOUBLE_CLICK_DURATION_MILLISECONDS {
double_click_select.write(DoubleClickSelect(selected_entity));
}
}
double_clicked.last_selected_entity = Some(selected_entity);
double_clicked.last_selected_time = current_time;
}
}