1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
//! "Pointer" into the draw tree.
use crate::*;
/// An [`Area`] can be thought of as a "pointer" into the draw tree. You typically
/// get one as the result of a draw command, like [`Cx::add_instances`],
/// or [`View::end_view`].
///
/// Note that an [`Area`] might point to an old element in the draw tree, so use
/// [`Area::is_valid`] to check if it points to the latest version.
///
/// You can use an [`Area`] pointer to write fields, e.g. using [`Area::get_slice_mut`],
/// [`Area::write_user_uniforms`], and so on. You
/// can also use it for checking if an event was fired on a certain part
/// of the draw tree (using [`Event::hits_pointer`], [`Cx::key_focus`], etc), and more.
///
/// TODO(JP): this can currently point to a [`View`]/[`CxView`] that isn't
/// actually in the draw tree anymore (ie. there the corresponding [`CxView`] is
/// never referenced in the draw tree), or which just doesn't exist at all any
/// more (ie. the [`View`] object has also been removed). There is currently no
/// was of telling if any of this is the case, since there is no "garbage
/// collection" of views. [`CxView`] just sticks around in [`Cx::views`] forever.
#[derive(Clone, Debug, Hash, PartialEq, Ord, PartialOrd, Eq, Copy)]
pub enum Area {
/// A "null pointer", doesn't point to anything yet.
Empty,
/// See [`ViewArea`].
View(ViewArea),
/// See [`InstanceRangeArea`].
InstanceRange(InstanceRangeArea),
}
impl Default for Area {
fn default() -> Area {
Area::Empty
}
}
/// Pointer to a particular view in the draw tree, using a [`ViewArea::view_id`]. Note
/// that a [`View::view_id`] only gets set when it gets drawn.
///
/// See also [`Area`].
#[derive(Clone, Default, Hash, Ord, PartialOrd, Eq, Debug, PartialEq, Copy)]
pub struct ViewArea {
/// Corresponds to [`View::view_id`] of the [`View`] this points to, which
/// is the same as the index of [`Cx::views`] of the corresponding [`CxView`].
pub(crate) view_id: usize,
/// The [`Cx::redraw_id`] during which this [`Area`] was created. If it
/// doesn't match the corresponding [`View::redraw_id`], then this pointer is
/// stale; it likely wasn't properly updated with [`Cx::request_draw`].
///
/// Note that if [`ViewArea::redraw_id`] doesn't match [`Cx::redraw_id`] then that doesn't
/// necessarily mean that the pointer is stale, since instead rendering for
/// the corresponding [`View`] could have been skipped (if nothing had
/// changed).
///
/// See also [`View::view_id`].
///
/// TODO(JP): Is the [`ViewArea`] redundant, since it basically contains the
/// same information as the [`View`] itself?
pub(crate) redraw_id: u64,
}
/// Pointer to a part of a [`DrawCall`], e.g. from [`Cx::add_instances`]. This pointer
/// points to a range of instances, where the first index is indicated by
/// [`InstanceRangeArea::instance_offset`], and the size of the
/// range by [`InstanceRangeArea::instance_count`].
///
/// See also [`Area`].
#[derive(Clone, Default, Hash, Ord, PartialOrd, Eq, Debug, PartialEq, Copy)]
pub struct InstanceRangeArea {
/// Corresponds to [`View::view_id`] of the [`View`] this points to, which
/// is the same as the index of [`Cx::views`] of the corresponding [`CxView`].
pub(crate) view_id: usize,
/// Index of [`CxView::draw_calls`] of the corresponding [`DrawCall`].
pub(crate) draw_call_id: usize,
/// Offset in "slots"/nibbles/4 bytes from the start of [`DrawCall::instances`]
/// to the first instance that this pointer describes.
pub(crate) instance_offset: usize,
/// Number of instances that this pointer describes.
pub(crate) instance_count: usize,
/// See [`ViewArea::redraw_id`].
pub(crate) redraw_id: u64,
}
impl Area {
/// Shorthand for `if let Area::Empty = area`.
pub fn is_empty(&self) -> bool {
if let Area::Empty = self {
return true;
}
false
}
/// Check if this is an [`Area::InstanceRange`] that points to the first instance
/// in its corresponding [`DrawCall`]. Useful for setting uniforms on a
/// [`DrawCall`] only once, when handling the first instance.
pub fn is_first_instance(&self) -> bool {
match self {
Area::InstanceRange(inst) => inst.instance_offset == 0,
_ => false,
}
}
/// Check if this [`Area`] points to valid data. Will return false for
/// [`Area::Empty`], and if the [`View::redraw_id`] is different
/// than the [`ViewArea::redraw_id`] or [`InstanceRangeArea::redraw_id`].
///
/// TODO(JP): this will return [`true`] when the [`Area`] points to data that
/// is not visible on the screen / when it's gone from the draw tree, or
/// even when the original [`View`] object is gone. That's probably bad, or at
/// least confusing. See [`Area`] for more information.
pub(crate) fn is_valid(&self, cx: &Cx) -> bool {
match self {
Area::InstanceRange(inst) => {
let cxview = &cx.views[inst.view_id];
if cxview.redraw_id != inst.redraw_id {
return false;
}
true
}
Area::View(view_area) => {
let cxview = &cx.views[view_area.view_id];
if cxview.redraw_id != view_area.redraw_id {
return false;
}
true
}
_ => false,
}
}
/// The scroll position of an [`Area`] is the cumulative offset of all scroll
/// containers (compared to if there had not been scrolling at all).
pub fn get_scroll_pos(&self, cx: &Cx) -> Vec2 {
if !self.is_valid(cx) {
panic!("get_scroll_pos was called for an invalid Area");
}
match self {
Area::InstanceRange(inst) => {
// Pull it directly out of the draw uniforms.
let cxview = &cx.views[inst.view_id];
let draw_call = &cxview.draw_calls[inst.draw_call_id];
Vec2 { x: draw_call.draw_uniforms.draw_scroll_x, y: draw_call.draw_uniforms.draw_scroll_y }
}
Area::View(view_area) => {
let cxview = &cx.views[view_area.view_id];
cxview.parent_scroll
}
_ => unreachable!(),
}
}
/// Returns the final screen [`Rect`] for the first instance of the [`Area`].
///
/// TODO(JP): The "first instance" bit is confusing; in most (if not all)
/// cases you'd want to get something that covers the entire [`Area`]. Maybe
/// returning a single [`Rect`] isn't the right thing then, since the
/// individual rectangles can be all over the place. We could return a [`Vec`]
/// instead?
///
/// TODO(JP): Specifically, this seems to return very weird values for
/// [`crate::TextIns`] (only the first character, and offset to the bottom it seems).
pub fn get_rect_for_first_instance(&self, cx: &Cx) -> Option<Rect> {
if !self.is_valid(cx) {
return None;
}
match self {
Area::InstanceRange(inst) => {
if inst.instance_count == 0 {
return None;
}
let cxview = &cx.views[inst.view_id];
let draw_call = &cxview.draw_calls[inst.draw_call_id];
assert!(!draw_call.instances.is_empty());
let sh = &cx.shaders[draw_call.shader_id];
if let Some(rect_pos) = sh.mapping.rect_instance_props.rect_pos {
let x = draw_call.instances[inst.instance_offset + rect_pos];
let y = draw_call.instances[inst.instance_offset + rect_pos + 1];
if let Some(rect_size) = sh.mapping.rect_instance_props.rect_size {
let w = draw_call.instances[inst.instance_offset + rect_size];
let h = draw_call.instances[inst.instance_offset + rect_size + 1];
return Some(draw_call.clip_and_scroll_rect(x, y, w, h));
}
}
None
}
Area::View(view_area) => {
let cxview = &cx.views[view_area.view_id];
Some(Rect { pos: cxview.rect.pos - cxview.parent_scroll, size: cxview.rect.size })
}
_ => None,
}
}
/// Get an immutable slice for an [`Area::InstanceRange`].
///
/// This is not safe when T cannot be initialized with arbitrary
/// data, so be careful. See also [`crate::cast`].
pub fn get_slice<T: 'static + Copy>(&self, cx: &Cx) -> &[T] {
if !self.is_valid(cx) {
return &mut [];
}
match self {
Area::InstanceRange(inst) => {
let cxview = &cx.views[inst.view_id];
let draw_call = &cxview.draw_calls[inst.draw_call_id];
let sh = &cx.shaders[draw_call.shader_id];
let total_instance_slots = sh.mapping.instance_props.total_slots;
let shader_bytes = total_instance_slots * std::mem::size_of::<f32>();
let struct_bytes = std::mem::size_of::<T>();
assert_eq!(
shader_bytes, struct_bytes,
"Mismatch between shader instance slots ({shader_bytes} bytes) and instance struct ({struct_bytes} bytes)"
);
// TODO(JP): Move to cast.rs?
unsafe {
std::slice::from_raw_parts(
draw_call.instances.as_ptr().add(inst.instance_offset) as *const T,
inst.instance_count,
)
}
}
_ => &mut [],
}
}
/// Get an immutable reference to the first element.
///
/// If no such element exists, then a default element is returned.
///
/// Example:
/// ```
/// let glyph = area.get_first::<TextIns>(cx);
/// ```
///
/// TODO(JP): It would be nice if we can eliminate the default fallback altogether;
/// see [`Cx::temp_default_data`] for ideas.
///
/// This is not safe when T cannot be initialized with arbitrary
/// data, so be careful. See also [`crate::cast`].
pub fn get_first<'a, T: 'static + Copy + Default>(&'a self, cx: &'a mut Cx) -> &'a T {
if let Some(first) = self.get_slice::<T>(cx).get(0) {
first
} else {
let len = cx.temp_default_data.len();
cx.temp_default_data.push(Box::new(T::default()));
// TODO(JP): Use https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_unchecked here
// once it's stable.
unsafe { cx.temp_default_data.get_unchecked(len).downcast_ref().unwrap() }
}
}
/// Get a mutable slice for an [`Area::InstanceRange`].
///
/// This is not safe when T cannot be initialized with arbitrary
/// data, so be careful. See also [`crate::cast`].
pub fn get_slice_mut<T: 'static + Copy>(&self, cx: &mut Cx) -> &mut [T] {
if !self.is_valid(cx) {
return &mut [];
}
match self {
Area::InstanceRange(inst) => {
let cxview = &mut cx.views[inst.view_id];
let draw_call = &mut cxview.draw_calls[inst.draw_call_id];
let sh = &cx.shaders[draw_call.shader_id];
let total_instance_slots = sh.mapping.instance_props.total_slots;
let shader_bytes = total_instance_slots * std::mem::size_of::<f32>();
let struct_bytes = std::mem::size_of::<T>();
assert_eq!(
shader_bytes, struct_bytes,
"Mismatch between shader instance slots ({shader_bytes} bytes) and instance struct ({struct_bytes} bytes)"
);
// If we have no instances, bail early so we don't mark the entire draw call as dirty.
if inst.instance_count == 0 {
return &mut [];
}
cx.passes[cxview.pass_id].paint_dirty = true;
draw_call.instance_dirty = true;
// TODO(JP): Move to cast.rs?
unsafe {
std::slice::from_raw_parts_mut(
draw_call.instances.as_mut_ptr().add(inst.instance_offset) as *mut T,
inst.instance_count,
)
}
}
_ => &mut [],
}
}
/// Get a mutable reference to the first element.
///
/// If no such element exists, then a default element is returned. Mutating such a default
/// element won't do anything, but it also won't hurt.
///
/// Note that in general you can't rely on these mutations to last very long, since they'll
/// be cleared on the next redraw.
///
/// Example:
/// ```
/// let glyph = area.get_first_mut::<TextIns>(cx);
/// ```
///
/// TODO(JP): It would be nice if we can eliminate the default fallback altogether;
/// see [`Cx::temp_default_data`] for ideas.
///
/// This is not safe when T cannot be initialized with arbitrary
/// data, so be careful. See also [`crate::cast`].
pub fn get_first_mut<'a, T: 'static + Copy + Default>(&'a self, cx: &'a mut Cx) -> &'a mut T {
if let Some(first) = self.get_slice_mut::<T>(cx).get_mut(0) {
first
} else {
let len = cx.temp_default_data.len();
cx.temp_default_data.push(Box::new(T::default()));
// TODO(JP): Use https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_unchecked here
// once it's stable.
unsafe { cx.temp_default_data.get_unchecked_mut(len).downcast_mut().unwrap() }
}
}
/// Get a write user-level uniforms for the [`DrawCall`] that this [`Area`] points to.
///
/// It can be useful to wrap this in [`Area::is_first_instance`] to avoid having to write
/// this multiple times for the same [`DrawCall`].
pub fn write_user_uniforms<T: 'static>(&self, cx: &mut Cx, uniforms: T) {
if !self.is_valid(cx) {
return;
}
match self {
Area::InstanceRange(inst) => {
let cxview = &mut cx.views[inst.view_id];
let draw_call = &mut cxview.draw_calls[inst.draw_call_id];
let shader_bytes = draw_call.user_uniforms.len() * std::mem::size_of::<f32>();
let struct_bytes = std::mem::size_of::<T>();
assert_eq!(
shader_bytes, struct_bytes,
"Mismatch between shader uniform slots ({shader_bytes} bytes) and instance struct ({struct_bytes} bytes)"
);
cx.passes[cxview.pass_id].paint_dirty = true;
draw_call.uniforms_dirty = true;
let data = unsafe { &mut *(draw_call.user_uniforms.as_mut_ptr() as *mut T) };
*data = uniforms;
}
_ => (),
}
}
/// Write a [`Texture`] value into the the [`DrawCall`] associated with this
/// [`Area::InstanceRange`].
///
/// TODO(JP): Fix bug: <https://github.com/Zaplib/zaplib/issues/156>
pub fn write_texture_2d(&self, cx: &mut Cx, name: &str, texture_handle: TextureHandle) {
if self.is_valid(cx) {
if let Area::InstanceRange(inst) = self {
let cxview = &mut cx.views[inst.view_id];
let draw_call = &mut cxview.draw_calls[inst.draw_call_id];
let sh = &cx.shaders[draw_call.shader_id];
for (index, prop) in sh.mapping.textures.iter().enumerate() {
if prop.name == name {
draw_call.textures_2d[index] = texture_handle.texture_id as u32;
return;
}
}
}
}
panic!("Cannot find texture2D prop {}", name)
}
}