bevy_ui/widget/
viewport.rs1use bevy_asset::Assets;
2use bevy_camera::Camera;
3#[cfg(feature = "bevy_ui_picking_backend")]
4use bevy_camera::NormalizedRenderTarget;
5use bevy_ecs::{
6 component::Component,
7 entity::Entity,
8 query::{Changed, Or},
9 reflect::ReflectComponent,
10 system::{Query, ResMut},
11};
12#[cfg(feature = "bevy_ui_picking_backend")]
13use bevy_ecs::{
14 message::MessageReader,
15 system::{Commands, Res},
16};
17use bevy_image::{Image, ToExtents};
18#[cfg(feature = "bevy_ui_picking_backend")]
19use bevy_math::Rect;
20use bevy_math::UVec2;
21#[cfg(feature = "bevy_ui_picking_backend")]
22use bevy_picking::{
23 events::PointerState,
24 hover::HoverMap,
25 pointer::{Location, PointerId, PointerInput, PointerLocation},
26};
27#[cfg(feature = "bevy_ui_picking_backend")]
28use bevy_platform::collections::HashMap;
29use bevy_reflect::Reflect;
30#[cfg(feature = "bevy_ui_picking_backend")]
31use bevy_transform::components::GlobalTransform;
32#[cfg(feature = "bevy_ui_picking_backend")]
33use uuid::Uuid;
34
35use crate::{ComputedNode, Node};
36
37#[derive(Component, Debug, Clone, Copy, Reflect)]
43#[reflect(Component, Debug)]
44#[require(Node)]
45#[cfg_attr(
46 feature = "bevy_ui_picking_backend",
47 require(PointerId::Custom(Uuid::new_v4()))
48)]
49pub struct ViewportNode {
50 pub camera: Entity,
54}
55
56impl ViewportNode {
57 #[inline]
59 pub const fn new(camera: Entity) -> Self {
60 Self { camera }
61 }
62}
63
64#[cfg(feature = "bevy_ui_picking_backend")]
65pub fn viewport_picking(
69 mut commands: Commands,
70 mut viewport_query: Query<(
71 Entity,
72 &ViewportNode,
73 &PointerId,
74 &mut PointerLocation,
75 &ComputedNode,
76 &GlobalTransform,
77 )>,
78 camera_query: Query<&Camera>,
79 hover_map: Res<HoverMap>,
80 pointer_state: Res<PointerState>,
81 mut pointer_inputs: MessageReader<PointerInput>,
82) {
83 let mut viewport_picks: HashMap<Entity, PointerId> = hover_map
85 .iter()
86 .flat_map(|(hover_pointer_id, hits)| {
87 hits.iter()
88 .filter(|(entity, _)| viewport_query.contains(**entity))
89 .map(|(entity, _)| (*entity, *hover_pointer_id))
90 })
91 .collect();
92
93 for ((pointer_id, _), pointer_state) in pointer_state.pointer_buttons.iter() {
95 for &target in pointer_state
96 .dragging
97 .keys()
98 .filter(|&entity| viewport_query.contains(*entity))
99 {
100 viewport_picks.insert(target, *pointer_id);
101 }
102 }
103
104 for (
105 viewport_entity,
106 &viewport,
107 &viewport_pointer_id,
108 mut viewport_pointer_location,
109 computed_node,
110 global_transform,
111 ) in &mut viewport_query
112 {
113 let Some(pick_pointer_id) = viewport_picks.get(&viewport_entity) else {
114 viewport_pointer_location.location = None;
116 continue;
117 };
118 let Ok(camera) = camera_query.get(viewport.camera) else {
119 continue;
120 };
121 let Some(cam_viewport_size) = camera.logical_viewport_size() else {
122 continue;
123 };
124
125 let node_rect = Rect::from_center_size(
127 global_transform.translation().truncate(),
128 computed_node.size(),
129 );
130 let top_left = node_rect.min * computed_node.inverse_scale_factor();
132 let logical_size = computed_node.size() * computed_node.inverse_scale_factor();
133
134 let Some(target) = camera.target.as_image() else {
135 continue;
136 };
137
138 for input in pointer_inputs
139 .read()
140 .filter(|input| &input.pointer_id == pick_pointer_id)
141 {
142 let local_position = (input.location.position - top_left) / logical_size;
143 let position = local_position * cam_viewport_size;
144
145 let location = Location {
146 position,
147 target: NormalizedRenderTarget::Image(target.clone().into()),
148 };
149 viewport_pointer_location.location = Some(location.clone());
150
151 commands.write_message(PointerInput {
152 location,
153 pointer_id: viewport_pointer_id,
154 action: input.action,
155 });
156 }
157 }
158}
159
160pub fn update_viewport_render_target_size(
162 viewport_query: Query<
163 (&ViewportNode, &ComputedNode),
164 Or<(Changed<ComputedNode>, Changed<ViewportNode>)>,
165 >,
166 camera_query: Query<&Camera>,
167 mut images: ResMut<Assets<Image>>,
168) {
169 for (viewport, computed_node) in &viewport_query {
170 let camera = camera_query.get(viewport.camera).unwrap();
171 let size = computed_node.size();
172
173 let Some(image_handle) = camera.target.as_image() else {
174 continue;
175 };
176 let size = size.as_uvec2().max(UVec2::ONE).to_extents();
177 images.get_mut(image_handle).unwrap().resize(size);
178 }
179}