Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/widget/viewport.rs
6599 views
1
use bevy_asset::Assets;
2
use bevy_camera::Camera;
3
#[cfg(feature = "bevy_ui_picking_backend")]
4
use bevy_camera::NormalizedRenderTarget;
5
use 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")]
13
use bevy_ecs::{
14
event::EventReader,
15
system::{Commands, Res},
16
};
17
use bevy_image::{Image, ToExtents};
18
#[cfg(feature = "bevy_ui_picking_backend")]
19
use bevy_math::Rect;
20
use bevy_math::UVec2;
21
#[cfg(feature = "bevy_ui_picking_backend")]
22
use bevy_picking::{
23
events::PointerState,
24
hover::HoverMap,
25
pointer::{Location, PointerId, PointerInput, PointerLocation},
26
};
27
#[cfg(feature = "bevy_ui_picking_backend")]
28
use bevy_platform::collections::HashMap;
29
use bevy_reflect::Reflect;
30
#[cfg(feature = "bevy_ui_picking_backend")]
31
use bevy_transform::components::GlobalTransform;
32
#[cfg(feature = "bevy_ui_picking_backend")]
33
use uuid::Uuid;
34
35
use crate::{ComputedNode, Node};
36
37
/// Component used to render a [`Camera::target`] to a node.
38
///
39
/// # See Also
40
///
41
/// [`update_viewport_render_target_size`]
42
#[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
)]
49
pub struct ViewportNode {
50
/// The entity representing the [`Camera`] associated with this viewport.
51
///
52
/// Note that removing the [`ViewportNode`] component will not despawn this entity.
53
pub camera: Entity,
54
}
55
56
impl ViewportNode {
57
/// Creates a new [`ViewportNode`] with a given `camera`.
58
#[inline]
59
pub const fn new(camera: Entity) -> Self {
60
Self { camera }
61
}
62
}
63
64
#[cfg(feature = "bevy_ui_picking_backend")]
65
/// Handles viewport picking logic.
66
///
67
/// Viewport entities that are being hovered or dragged will have all pointer inputs sent to them.
68
pub 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: EventReader<PointerInput>,
82
) {
83
// Handle hovered entities.
84
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
// Handle dragged entities, which need to be considered for dragging in and out of viewports.
94
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
// Lift the viewport pointer if it's not being used.
115
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
// Create a `Rect` in *physical* coordinates centered at the node's GlobalTransform
126
let node_rect = Rect::from_center_size(
127
global_transform.translation().truncate(),
128
computed_node.size(),
129
);
130
// Location::position uses *logical* coordinates
131
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_event(PointerInput {
152
location,
153
pointer_id: viewport_pointer_id,
154
action: input.action,
155
});
156
}
157
}
158
}
159
160
/// Updates the size of the associated render target for viewports when the node size changes.
161
pub 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
}
180
181