Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/widget/viewport.rs
9395 views
1
#[cfg(feature = "bevy_picking")]
2
use crate::UiGlobalTransform;
3
use crate::{ComputedNode, Node};
4
use bevy_asset::Assets;
5
#[cfg(feature = "bevy_picking")]
6
use bevy_camera::Camera;
7
use bevy_camera::RenderTarget;
8
use bevy_ecs::{
9
component::Component,
10
entity::Entity,
11
query::{Changed, Or},
12
reflect::ReflectComponent,
13
system::{Query, ResMut},
14
};
15
#[cfg(feature = "bevy_picking")]
16
use bevy_ecs::{
17
message::MessageReader,
18
system::{Commands, Res},
19
};
20
use bevy_image::{Image, ToExtents};
21
use bevy_math::UVec2;
22
#[cfg(feature = "bevy_picking")]
23
use bevy_picking::{
24
events::PointerState,
25
hover::HoverMap,
26
pointer::{Location, PointerId, PointerInput, PointerLocation},
27
};
28
use bevy_reflect::Reflect;
29
30
/// Component used to render a [`RenderTarget`] to a node.
31
///
32
/// # See Also
33
///
34
/// [`update_viewport_render_target_size`]
35
#[derive(Component, Debug, Clone, Copy, Reflect)]
36
#[reflect(Component, Debug)]
37
#[require(Node)]
38
#[cfg_attr(
39
feature = "bevy_picking",
40
require(PointerId::Custom(uuid::Uuid::new_v4()))
41
)]
42
pub struct ViewportNode {
43
/// The entity representing the [`Camera`] associated with this viewport.
44
///
45
/// Note: Removing the [`ViewportNode`] component will not despawn this
46
/// entity.
47
///
48
/// Note: Despawning the camera entity will leave a viewport node with an
49
/// invalid camera.
50
pub camera: Entity,
51
}
52
53
impl ViewportNode {
54
/// Creates a new [`ViewportNode`] with a given `camera`.
55
#[inline]
56
pub const fn new(camera: Entity) -> Self {
57
Self { camera }
58
}
59
}
60
61
#[cfg(feature = "bevy_picking")]
62
/// Handles viewport picking logic.
63
///
64
/// Viewport entities that are being hovered or dragged will have all pointer inputs sent to them.
65
pub fn viewport_picking(
66
mut commands: Commands,
67
mut viewport_query: Query<(
68
Entity,
69
&ViewportNode,
70
&PointerId,
71
&mut PointerLocation,
72
&ComputedNode,
73
&UiGlobalTransform,
74
)>,
75
camera_query: Query<(&Camera, &RenderTarget)>,
76
hover_map: Res<HoverMap>,
77
pointer_state: Res<PointerState>,
78
mut pointer_inputs: MessageReader<PointerInput>,
79
) {
80
use bevy_camera::NormalizedRenderTarget;
81
use bevy_math::Rect;
82
use bevy_platform::collections::HashMap;
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, render_target)) = 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 =
127
Rect::from_center_size(global_transform.translation.trunc(), computed_node.size());
128
// Location::position uses *logical* coordinates
129
let top_left = node_rect.min * computed_node.inverse_scale_factor();
130
let logical_size = computed_node.size() * computed_node.inverse_scale_factor();
131
132
let Some(target) = render_target.as_image() else {
133
continue;
134
};
135
136
for input in pointer_inputs
137
.read()
138
.filter(|input| &input.pointer_id == pick_pointer_id)
139
{
140
let local_position = (input.location.position - top_left) / logical_size;
141
let position = local_position * cam_viewport_size;
142
143
let location = Location {
144
position,
145
target: NormalizedRenderTarget::Image(target.clone().into()),
146
};
147
viewport_pointer_location.location = Some(location.clone());
148
149
commands.write_message(PointerInput {
150
location,
151
pointer_id: viewport_pointer_id,
152
action: input.action,
153
});
154
}
155
}
156
}
157
158
/// Updates the size of the associated render target for viewports when the node size changes.
159
pub fn update_viewport_render_target_size(
160
viewport_query: Query<
161
(&ViewportNode, &ComputedNode),
162
Or<(Changed<ComputedNode>, Changed<ViewportNode>)>,
163
>,
164
camera_query: Query<&RenderTarget>,
165
mut images: ResMut<Assets<Image>>,
166
) {
167
for (viewport, computed_node) in &viewport_query {
168
let Ok(render_target) = camera_query.get(viewport.camera) else {
169
continue;
170
};
171
let size = computed_node.size();
172
173
let Some(image_handle) = render_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