/ widgets / src / button.rs
button.rs
  1  use std::time::{Duration, Instant};
  2  
  3  use anyhow::{Result, anyhow};
  4  use glam::{Vec2, Vec3Swizzles, vec2, vec3};
  5  use let_engine::{
  6      core_backend::gpu::{GpuInterface, Loaded},
  7      events::{ElementState, InputEvent},
  8      objects::{
  9          Appearance, AppearanceBuilder, Color, Descriptor, ObjectBuilder, ObjectId, Transform,
 10      },
 11      prelude::{
 12          AppearanceBuilderError, GraphicsShaders, LoadedBuffer, LoadedModel, LoadedTexture,
 13          MaterialSettings, ViewTypeDim, tvert,
 14      },
 15      resources::{
 16          buffer::{Buffer, BufferAccess, BufferUsage, Location, PreferOperation},
 17          data::TVert,
 18          material::Material,
 19          model::Model,
 20      },
 21      scenes::{LayerViewId, Scene},
 22  };
 23  
 24  use crate::{
 25      TextureSection, WidgetManager,
 26      label::{Label, LabelBuilder},
 27  };
 28  
 29  #[derive(Clone)]
 30  pub struct Button<T: Loaded + 'static> {
 31      object_id: ObjectId,
 32      label: Option<(ObjectId, Label<T>)>,
 33      view_id: LayerViewId,
 34  
 35      resources: ButtonResources<T>,
 36      variant: ButtonVariant<T>,
 37  
 38      bounding_box: BoundingBox,
 39      transform: Transform,
 40  
 41      state: ButtonState,
 42      pub(crate) handle: bool,
 43  }
 44  
 45  #[derive(Clone, Default)]
 46  pub struct ButtonBuilder<T: Loaded> {
 47      pub variant: ButtonVariant<T>,
 48  
 49      /// The optional label tied to this button.
 50      ///
 51      /// - If the variant is `solid`, `color` of this builder is overridden.
 52      pub label_builder: Option<LabelBuilder>,
 53  
 54      pub bounding_box: BoundingBox,
 55  
 56      /// The transform of the bounding box.
 57      pub transform: Transform,
 58  
 59      pub object_transform: Transform,
 60  }
 61  
 62  impl<T: Loaded> ButtonBuilder<T> {
 63      /// Sets `variant` and returns self
 64      pub fn variant(mut self, variant: ButtonVariant<T>) -> Self {
 65          self.variant = variant;
 66          self
 67      }
 68  
 69      /// Sets the optional label of this object and returns self
 70      pub fn label(mut self, builder: LabelBuilder) -> Self {
 71          self.label_builder = Some(builder);
 72          self
 73      }
 74  
 75      /// Sets `bounding_box` and returns self
 76      pub fn bounding_box(mut self, bounding_box: impl Into<BoundingBox>) -> Self {
 77          self.bounding_box = bounding_box.into();
 78          self
 79      }
 80  
 81      /// Sets the transform of the bounding box and solid appearance, then returns self
 82      pub fn transform(mut self, transform: Transform) -> Self {
 83          self.transform = transform;
 84          self
 85      }
 86  
 87      /// Sets the transform of the object and returns self
 88      pub fn object_transform(mut self, transform: Transform) -> Self {
 89          self.object_transform = transform;
 90          self
 91      }
 92  }
 93  
 94  slotmap::new_key_type! {
 95      pub struct ButtonId;
 96  }
 97  
 98  #[derive(Clone)]
 99  enum ButtonResources<T: Loaded> {
100      None,
101  
102      /// Resources necessary for the solid button variation
103      Solid {
104          updating: bool,
105          last_color: Color,
106          last_label_color: Color,
107          buffer: T::BufferId<Color>,
108      },
109  
110      /// Resources necessary for the simple textured button variation
111      Textured {
112          updating: bool,
113          virtual_texture_id: T::TextureId,
114          model_id: T::ModelId<TVert>,
115      },
116  
117      Tiled {
118          last_size: Vec2,
119          texture_dimensions: Vec2,
120  
121          buffer_id: T::BufferId<u32>,
122          model_id: T::ModelId<TVert>,
123      },
124  }
125  
126  #[derive(Debug, thiserror::Error)]
127  pub enum ButtonBuilderError<T: Loaded> {
128      #[error("Validation error: {0}")]
129      ValidationError(&'static str),
130  
131      #[error("Error while creating appearance for object: {0}")]
132      AppearanceError(AppearanceBuilderError<T>),
133  }
134  
135  impl<T: Loaded> ButtonBuilder<T> {
136      /// Builds and adds this button to the scene.
137      pub fn build(
138          self,
139          view_id: LayerViewId,
140          scene: &mut Scene<T>,
141          widget_manager: &mut WidgetManager<T>,
142          gpu_interface: &impl GpuInterface<T>,
143      ) -> Result<ButtonId, ButtonBuilderError<T>> {
144          let button = self.create_button(view_id, scene, widget_manager, gpu_interface)?;
145          Ok(widget_manager.buttons.insert(button))
146      }
147  
148      pub(crate) fn create_button(
149          mut self,
150          view_id: LayerViewId,
151          scene: &mut Scene<T>,
152          widget_manager: &mut WidgetManager<T>,
153          gpu_interface: &impl GpuInterface<T>,
154      ) -> Result<Button<T>, ButtonBuilderError<T>> {
155          let variant = self.variant;
156          let bounding_box = self.bounding_box;
157          let transform = self.transform;
158  
159          let mut object_builder = ObjectBuilder::with_transform(transform);
160  
161          // Initialize appearance
162          let (appearance, resources) = match &variant {
163              ButtonVariant::Invisible => (None, ButtonResources::None),
164              ButtonVariant::Solid {
165                  regular,
166                  regular_label,
167                  ..
168              } => {
169                  if let Some(builder) = self.label_builder.as_mut() {
170                      builder.text_color = *regular_label;
171                  }
172                  // Initialize with regular appearance
173                  Self::solid_appearance(gpu_interface, *regular, *regular_label, transform)
174              }
175              ButtonVariant::Textured(ButtonTexture { regular, .. }) => {
176                  // Initialize with regular texture
177                  Self::textured_appearance(gpu_interface, regular, transform)
178              }
179              ButtonVariant::Tiled { texture_id } => {
180                  Self::tiled_appearance(gpu_interface, *texture_id, transform)
181              }
182              ButtonVariant::Custom(appearance) => (Some(appearance.clone()), ButtonResources::None),
183          };
184  
185          object_builder.appearance = appearance;
186  
187          let view = scene
188              .view(view_id)
189              .ok_or(ButtonBuilderError::ValidationError(
190                  "The provided layer view does not exist.",
191              ))?;
192  
193          // Build object
194          let object_id = scene.add_object(view.layer_id(), object_builder).ok_or(
195              ButtonBuilderError::ValidationError("The provided layer does not exist."),
196          )?;
197  
198          scene[object_id].transform = self.object_transform;
199  
200          let label = if let Some(builder) = self.label_builder {
201              let label = builder.build(widget_manager, gpu_interface).unwrap();
202              let appearance = label
203                  .appearance(gpu_interface)
204                  .map_err(ButtonBuilderError::AppearanceError)?;
205              let object_id = scene
206                  .add_object_with_parent(object_id, ObjectBuilder::with_appearance(appearance))
207                  .unwrap();
208              Some((object_id, label))
209          } else {
210              None
211          };
212  
213          Ok(Button {
214              object_id,
215              label,
216              view_id,
217  
218              resources,
219              variant,
220              bounding_box,
221              transform,
222  
223              state: ButtonState::Released(Instant::now()),
224              handle: false,
225          })
226      }
227  
228      fn solid_appearance(
229          interface: &impl GpuInterface<T>,
230          color: Color,
231          label_color: Color,
232          mut transform: Transform,
233      ) -> (Option<Appearance<T>>, ButtonResources<T>) {
234          let model = interface.load_model(&Model::square()).unwrap();
235          let material = interface
236              .load_material::<Vec2>(&Material::new_default())
237              .unwrap();
238  
239          let buffer = interface
240              .load_buffer(&Buffer::from_data(
241                  BufferUsage::Uniform,
242                  BufferAccess::Pinned(PreferOperation::Write),
243                  color,
244              ))
245              .unwrap();
246  
247          transform.position.z += 0.1;
248  
249          let builder = AppearanceBuilder::default()
250              .model(model)
251              .material(material)
252              .transparent(color.is_transparent())
253              .transform(transform)
254              .descriptors(&[
255                  (Location::new(0, 0), Descriptor::<T>::Mvp),
256                  (Location::new(1, 0), Descriptor::buffer(buffer)),
257              ]);
258  
259          if let Ok(result) = builder.build(interface) {
260              (
261                  Some(result),
262                  ButtonResources::Solid {
263                      updating: false,
264                      last_color: color,
265                      last_label_color: label_color,
266                      buffer,
267                  },
268              )
269          } else {
270              panic!("Could not create button appearance.");
271          }
272      }
273  
274      fn textured_appearance(
275          interface: &impl GpuInterface<T>,
276          texture: &TextureSection<T>,
277          mut transform: Transform,
278      ) -> (Option<Appearance<T>>, ButtonResources<T>) {
279          let texture_id = interface.add_virtual_texture(texture.texture_id).unwrap();
280          let (vertices, indices) = texture.vertices_indices();
281  
282          let model_id = interface
283              .load_model(&Model::new_indexed(
284                  vertices.to_vec(),
285                  indices.to_vec(),
286                  BufferAccess::Staged,
287              ))
288              .unwrap();
289  
290          let material = interface
291              .load_material::<TVert>(&Material::default_textured())
292              .unwrap();
293  
294          let buffer = interface
295              .load_buffer(&Buffer::from_data(
296                  BufferUsage::Uniform,
297                  BufferAccess::Pinned(PreferOperation::Write),
298                  Color::WHITE,
299              ))
300              .unwrap();
301  
302          transform.position.z += 0.1;
303  
304          let builder = AppearanceBuilder::default()
305              .model(model_id)
306              .material(material)
307              .transform(transform)
308              .transparent(true)
309              .descriptors(&[
310                  (Location::new(0, 0), Descriptor::<T>::Mvp),
311                  (Location::new(1, 0), Descriptor::buffer(buffer)),
312                  (Location::new(2, 0), Descriptor::Texture(texture_id)),
313              ]);
314  
315          if let Ok(result) = builder.build(interface) {
316              (
317                  Some(result),
318                  ButtonResources::Textured {
319                      updating: false,
320                      virtual_texture_id: texture_id,
321                      model_id,
322                  },
323              )
324          } else {
325              panic!("Could not create button appearance.");
326          }
327      }
328  
329      fn tiled_appearance(
330          interface: &impl GpuInterface<T>,
331          texture_id: T::TextureId,
332          transform: Transform,
333      ) -> (Option<Appearance<T>>, ButtonResources<T>) {
334          let texture = interface
335              .texture(texture_id)
336              .expect("Provided texture ID is invalid.");
337          let ViewTypeDim::D2Array { x, y, .. } = *texture.dimensions() else {
338              panic!("Expected provided texture dimensions to be a 2 dimensional texture array.");
339          };
340  
341          let (vertices, indices) =
342              tiled_vertices_indices(transform.size.xy(), vec2(x as f32, y as f32));
343  
344          let model = Model::new_indexed(vertices.to_vec(), indices.to_vec(), BufferAccess::Staged);
345  
346          let model_id = interface.load_model(&model).unwrap();
347  
348          let material = interface
349              .load_material::<TVert>(&Material::new(
350                  MaterialSettings::default(),
351                  GraphicsShaders::new(
352                      include_bytes!("shaders/tiled_button_vert.spv").to_vec(),
353                      "main".to_owned(),
354                      include_bytes!("shaders/tiled_button_frag.spv").to_vec(),
355                      "main".to_owned(),
356                  ),
357              ))
358              .unwrap();
359  
360          let buffer_id = interface
361              .load_buffer(&Buffer::from_data(
362                  BufferUsage::Uniform,
363                  BufferAccess::Pinned(PreferOperation::Write),
364                  0u32,
365              ))
366              .unwrap();
367  
368          let builder = AppearanceBuilder::default()
369              .model(model_id)
370              .material(material)
371              .transform(Transform::with_position(vec3(0.0, 0.0, 0.11)))
372              .transparent(true)
373              .descriptors(&[
374                  (Location::new(0, 0), Descriptor::<T>::Mvp),
375                  (Location::new(1, 0), Descriptor::buffer(buffer_id)),
376                  (Location::new(2, 0), Descriptor::Texture(texture_id)),
377              ]);
378  
379          if let Ok(result) = builder.build(interface) {
380              (
381                  Some(result),
382                  ButtonResources::Tiled {
383                      last_size: transform.size.xy(),
384                      texture_dimensions: vec2(x as f32, y as f32),
385                      buffer_id,
386                      model_id,
387                  },
388              )
389          } else {
390              panic!("Could not create button appearance.");
391          }
392      }
393  }
394  
395  impl<T: Loaded + 'static> Button<T> {
396      pub(super) fn update(&mut self, interface: &impl GpuInterface<T>) -> Result<()> {
397          match &mut self.resources {
398              ButtonResources::None => Ok(()),
399              ButtonResources::Solid {
400                  updating,
401                  last_color,
402                  last_label_color,
403                  buffer,
404              } => {
405                  if !*updating {
406                      return Ok(());
407                  }
408                  let ButtonVariant::Solid {
409                      interp,
410                      regular,
411                      hovered,
412                      pressed,
413                      regular_label,
414                      hovered_label,
415                      pressed_label,
416                  } = &self.variant
417                  else {
418                      panic!("Expected `ButtonVariant` to be solid")
419                  };
420  
421                  let buffer = interface
422                      .buffer(*buffer)
423                      .ok_or(anyhow!("Buffer not found"))?;
424  
425                  match self.state {
426                      ButtonState::Released(_) => {
427                          let p = self.state.animation_completion(*interp);
428                          buffer.write_data(|color| *color = last_color.lerp(*regular, p))?;
429                          if let Some((_, label)) = self.label.as_mut() {
430                              label.update_text_color(last_label_color.lerp(*regular_label, p))?;
431                          }
432                          if p >= 1.0 {
433                              *updating = false;
434                          }
435                      }
436                      ButtonState::Hovered(_) => {
437                          let p = self.state.animation_completion(*interp);
438                          buffer.write_data(|color| *color = last_color.lerp(*hovered, p))?;
439                          if let Some((_, label)) = self.label.as_mut() {
440                              label.update_text_color(last_label_color.lerp(*hovered_label, p))?;
441                          }
442                          if p >= 1.0 {
443                              *updating = false;
444                          }
445                      }
446                      ButtonState::Down(_) => {
447                          buffer.write_data(|color| *color = *pressed)?;
448                          if let Some((_, label)) = self.label.as_mut() {
449                              label.update_text_color(*pressed_label)?;
450                          }
451                          *updating = false;
452                      }
453                  }
454                  Ok(())
455              }
456              ButtonResources::Textured {
457                  updating,
458                  virtual_texture_id,
459                  model_id,
460              } => {
461                  if !*updating {
462                      return Ok(());
463                  }
464                  let ButtonVariant::Textured(ButtonTexture {
465                      regular,
466                      hovered,
467                      pressed,
468                  }) = &self.variant
469                  else {
470                      panic!("Expected `ButtonVariant` to be textured");
471                  };
472  
473                  let model = interface
474                      .model(*model_id)
475                      .ok_or(anyhow!("Model not found."))?;
476  
477                  let (vertices, texture_id) = match self.state {
478                      ButtonState::Released(_) => (regular.vertices(), regular.texture_id),
479                      ButtonState::Hovered(_) => (hovered.vertices(), hovered.texture_id),
480                      ButtonState::Down(_) => (pressed.vertices(), pressed.texture_id),
481                  };
482  
483                  model.write_vertices(|write| write.copy_from_slice(&vertices), 4)?;
484  
485                  interface.map_virtual_texture(*virtual_texture_id, texture_id)?;
486  
487                  *updating = false;
488  
489                  Ok(())
490              }
491              ButtonResources::Tiled {
492                  last_size,
493                  texture_dimensions,
494                  buffer_id,
495                  model_id,
496              } => {
497                  let size = self.transform.size.xy();
498                  if size == *last_size {
499                      return Ok(());
500                  }
501                  *last_size = size;
502  
503                  let model = interface
504                      .model(*model_id)
505                      .ok_or(anyhow!("Model not found."))?;
506  
507                  let buffer = interface
508                      .buffer(*buffer_id)
509                      .ok_or(anyhow!("Buffer not found."))?;
510  
511                  let (vertices, _) = tiled_vertices_indices(size, *texture_dimensions);
512  
513                  model
514                      .write_vertices(|write| write.copy_from_slice(&vertices), vertices.len())
515                      .unwrap();
516  
517                  let index = match self.state {
518                      ButtonState::Released(_) => 0,
519                      ButtonState::Hovered(_) => 1,
520                      ButtonState::Down(_) => 2,
521                  };
522  
523                  buffer.write_data(|write| *write = index).unwrap();
524  
525                  Ok(())
526              }
527          }
528      }
529  
530      pub(crate) fn update_state(&mut self, state: ButtonState) {
531          match &mut self.resources {
532              ButtonResources::Solid { updating, .. }
533              | ButtonResources::Textured { updating, .. } => *updating = true,
534              ButtonResources::Tiled { last_size, .. } => *last_size = Vec2::ZERO,
535              _ => (),
536          }
537  
538          if let ButtonResources::Solid {
539              last_color,
540              last_label_color,
541              ..
542          } = &mut self.resources
543              && let ButtonVariant::Solid {
544                  interp,
545                  regular,
546                  hovered,
547                  pressed,
548                  regular_label,
549                  hovered_label,
550                  pressed_label,
551              } = &self.variant
552          {
553              let p = self.state.animation_completion(*interp);
554              let (color, label_color) = match self.state {
555                  ButtonState::Released(_) => (regular, regular_label),
556                  ButtonState::Hovered(_) => (hovered, hovered_label),
557                  ButtonState::Down(_) => (pressed, pressed_label),
558              };
559              if let Some((_, label)) = self.label.as_mut() {
560                  label
561                      .update_text_color(last_label_color.lerp(*label_color, p))
562                      .expect("Expected widget manager to not be destroyed.");
563              }
564              *last_color = last_color.lerp(*color, p);
565          };
566  
567          self.state = state;
568      }
569  
570      pub(super) fn update_cursor_pos(
571          &mut self,
572          cursor_world_position: Vec2,
573          global_transform: Transform,
574      ) {
575          // * If cursor lies within the button
576          if self
577              .bounding_box
578              .test(cursor_world_position, global_transform)
579          {
580              if !self.state.hovered() {
581                  self.update_state(self.state.hover());
582              }
583          } else if self.state.hovered() {
584              self.update_state(self.state.release());
585          }
586      }
587  }
588  
589  impl<T: Loaded + 'static> Button<T> {
590      pub(super) fn update_input(&mut self, event: &InputEvent) {
591          if let InputEvent::MouseInput(let_engine::input::MouseButton::Left, element_state) = event {
592              match element_state {
593                  ElementState::Pressed => {
594                      if self.state.hovered() {
595                          self.update_state(self.state.press());
596                      }
597                  }
598                  ElementState::Released => {
599                      if let ButtonState::Down(_) = self.state {
600                          self.handle = true;
601                          self.update_state(self.state.hover());
602                      }
603                  }
604              }
605          }
606      }
607  
608      /// Returns this buttons object ID
609      pub fn object_id(&self) -> ObjectId {
610          self.object_id
611      }
612  
613      /// Returns the ID of the label object optionally present within this button.
614      pub fn label_object_id(&self) -> Option<ObjectId> {
615          self.label.as_ref().map(|(id, _)| *id)
616      }
617  
618      /// Returns the optional label within this button.
619      pub fn label(&self) -> Option<&Label<T>> {
620          self.label.as_ref().map(|(_, label)| label)
621      }
622  
623      /// Returns the optional label within this button mutably.
624      pub fn label_mut(&mut self) -> Option<&mut Label<T>> {
625          self.label.as_mut().map(|(_, label)| label)
626      }
627  
628      /// Returns this buttons layer view ID
629      pub fn view_id(&self) -> LayerViewId {
630          self.view_id
631      }
632  
633      /// Returns the bounding box of this button.
634      pub fn bounding_box(&self) -> &BoundingBox {
635          &self.bounding_box
636      }
637  
638      /// Sets the bounding box of this button.
639      pub fn set_bounding_box(&mut self, bounding_box: BoundingBox) {
640          self.bounding_box = bounding_box;
641      }
642  
643      /// Returns the transform of this button.
644      pub fn transform(&self) -> &Transform {
645          &self.transform
646      }
647  
648      /// Returns the mutable transform of this button.
649      pub fn transform_mut(&mut self) -> &mut Transform {
650          &mut self.transform
651      }
652  
653      /// Sets the transform of this button.
654      pub fn set_transform(&mut self, transform: Transform) {
655          self.transform = transform;
656      }
657  
658      /// Returns the current button state.
659      #[inline]
660      pub fn state(&self) -> ButtonState {
661          self.state
662      }
663  
664      /// Sets the button to the pressed state.
665      pub fn press(&mut self) {
666          self.update_state(self.state.press());
667      }
668  
669      /// Releases the button in case it was pressed.
670      pub fn release(&mut self) {
671          // This if exists for the chance the button is hovered.
672          if let ButtonState::Down(_) = self.state {
673              self.update_state(self.state.release());
674          }
675      }
676  
677      /// Peeks if there is a new event without handling it.
678      #[inline]
679      pub fn peek_event(&self) -> bool {
680          self.handle
681      }
682  
683      /// Handles the button event, returning true if there is a new event.
684      #[inline]
685      pub fn handle(&mut self) -> bool {
686          std::mem::take(&mut self.handle)
687      }
688  }
689  
690  /// The current state of the button.
691  #[derive(Clone, Copy, Debug)]
692  pub enum ButtonState {
693      /// The button is not hovered or down.
694      /// The instant represents since when it has been in this state.
695      Released(Instant),
696  
697      /// The cursor is currently hovering this button since the provided instant.
698      Hovered(Instant),
699  
700      /// The button has been down since the provided instant.
701      Down(Instant),
702  }
703  
704  impl Default for ButtonState {
705      fn default() -> Self {
706          Self::Released(Instant::now())
707      }
708  }
709  
710  impl ButtonState {
711      #[inline]
712      pub fn release(self) -> Self {
713          Self::Released(Instant::now())
714      }
715  
716      #[inline]
717      pub fn hover(self) -> Self {
718          Self::Hovered(Instant::now())
719      }
720  
721      #[inline]
722      pub fn press(self) -> Self {
723          Self::Down(Instant::now())
724      }
725  
726      pub fn instant(&self) -> Instant {
727          match self {
728              ButtonState::Released(instant)
729              | ButtonState::Hovered(instant)
730              | ButtonState::Down(instant) => *instant,
731          }
732      }
733  
734      /// Returns the given animation completion percentage from the given animation duration
735      pub fn animation_completion(&self, duration: Duration) -> f32 {
736          if duration.is_zero() {
737              1.0
738          } else {
739              let dur = duration.as_secs_f32();
740              match self {
741                  ButtonState::Released(instant) => instant.elapsed().as_secs_f32() / dur,
742                  ButtonState::Hovered(instant) => instant.elapsed().as_secs_f32() / dur,
743                  ButtonState::Down(_) => 1.0,
744              }
745          }
746          .clamp(0.0, 1.0)
747      }
748  
749      /// Returns true if the cursor currently hovers this button.
750      /// This also applies if the button is down.
751      #[inline]
752      pub fn hovered(&self) -> bool {
753          matches!(self, Self::Hovered(_) | Self::Down(_))
754      }
755  
756      /// Returns true if this button is currently down.
757      #[inline]
758      pub fn down(&self) -> bool {
759          matches!(self, Self::Down(_))
760      }
761  }
762  
763  /// The type of the button.
764  #[derive(Clone)]
765  pub enum ButtonVariant<T: Loaded> {
766      /// The button is invisible.
767      Invisible,
768  
769      /// The button is a simple quad filled with a solid color.
770      ///
771      /// There is a linear interpolation duration for a smooth transition.
772      /// There is no interpolation step for pressing the button down.
773      ///
774      /// This is the default selection with a default color selection.
775      Solid {
776          interp: Duration,
777          regular: Color,
778          hovered: Color,
779          pressed: Color,
780          regular_label: Color,
781          hovered_label: Color,
782          pressed_label: Color,
783      },
784  
785      /// The button has a simple texture.
786      Textured(ButtonTexture<T>),
787  
788      /// The button has a tiled texture that can be resized.
789      Tiled {
790          /// A texture with 9 layers
791          ///
792          /// The layers must be in this order:
793          /// - 0: regular side
794          /// - 1: hovered side
795          /// - 2: pressed side
796          /// - 3: regular corner
797          /// - 4: hovered corner
798          /// - 5: pressed corner
799          /// - 6: regular center
800          /// - 7: hovered center
801          /// - 8: pressed center
802          texture_id: T::TextureId,
803      },
804  
805      /// The button has a completely custom appearance.
806      Custom(Appearance<T>),
807  }
808  
809  impl<T: Loaded> Default for ButtonVariant<T> {
810      fn default() -> Self {
811          Self::Solid {
812              interp: Duration::from_millis(200),
813              regular: Color::from_rgba(0.3, 0.3, 0.3, 0.5),
814              hovered: Color::from_rgba(0.7, 0.7, 0.7, 0.9),
815              pressed: Color::from_rgb(0.4, 0.4, 0.4),
816              regular_label: Color::from_rgba(0.8, 0.8, 0.8, 0.7),
817              hovered_label: Color::from_rgb(1.0, 1.0, 1.0),
818              pressed_label: Color::from_rgb(0.5, 0.5, 0.5),
819          }
820      }
821  }
822  
823  /// The three variations of the button texture.
824  #[derive(Clone)]
825  pub struct ButtonTexture<T: Loaded> {
826      /// When not hovered/pressed
827      pub regular: TextureSection<T>,
828  
829      /// When the cursor hovers over the button
830      pub hovered: TextureSection<T>,
831  
832      /// When the cursor hovers over the button and a mouse button is down.
833      pub pressed: TextureSection<T>,
834  }
835  
836  /// The bounding box position is affected by the objects scene position,
837  /// but not its appearance position.
838  ///
839  /// By default this is a bounding box quad
840  #[derive(Debug, Clone, Default)]
841  pub enum BoundingBox {
842      /// A simple Quad
843      #[default]
844      Quad,
845  
846      /// A circle.
847      Circle,
848  
849      /// Centered model composed of 2D triangles
850      Custom(Box<[Vec2]>),
851  }
852  
853  impl From<Box<[Vec2]>> for BoundingBox {
854      fn from(value: Box<[Vec2]>) -> Self {
855          Self::Custom(value)
856      }
857  }
858  
859  impl From<Vec<Vec2>> for BoundingBox {
860      fn from(value: Vec<Vec2>) -> Self {
861          Self::Custom(value.into_boxed_slice())
862      }
863  }
864  
865  impl From<&[Vec2]> for BoundingBox {
866      fn from(value: &[Vec2]) -> Self {
867          value.to_vec().into()
868      }
869  }
870  
871  impl BoundingBox {
872      /// Test if the given 2D world pos point lies within the bounding box.
873      ///
874      /// Model is expected to be a multiple of 3.
875      pub fn test(&self, position: Vec2, bounding_box_transform: Transform) -> bool {
876          // Translate point to bounding box space
877          let local_pos = {
878              let inv = bounding_box_transform.to_matrix().inverse();
879              let p = inv * position.extend(0.0).extend(1.0);
880              p.truncate().xy()
881          };
882  
883          match self {
884              Self::Quad => {
885                  let Vec2 { x, y } = local_pos.abs();
886  
887                  x <= 1.0 && y <= 1.0
888              }
889              Self::Circle => local_pos.length_squared() <= 1.0,
890              Self::Custom(model) => {
891                  debug_assert!(model.len().is_multiple_of(3));
892                  for tri in model.chunks_exact(3) {
893                      let [a, b, c] = tri else { unreachable!() };
894  
895                      let s1 = (b - a).perp_dot(local_pos - a);
896                      let s2 = (c - b).perp_dot(local_pos - b);
897                      let s3 = (a - c).perp_dot(local_pos - c);
898  
899                      const EPS: f32 = 1e-6;
900                      if (s1 >= -EPS && s2 >= -EPS && s3 >= -EPS)
901                          || (s1 <= EPS && s2 <= EPS && s3 <= EPS)
902                      {
903                          return true;
904                      }
905                  }
906  
907                  false
908              }
909          }
910      }
911  }
912  
913  fn tiled_vertices_indices(size: Vec2, dimensions: Vec2) -> ([TVert; 36], [u32; 54]) {
914      let Vec2 {
915          x: w, // width
916          y: h, // height
917      } = size;
918      let Vec2 { x, y } = dimensions; // texture dimensions
919      let Vec2 { x: u, y: v } = (size - dimensions) / dimensions; // scaled uv
920  
921      // y
922      // (-1.0) 3---2-----7---6
923      // |      |   |     |   |
924      // |y     0---1     4---5
925      // |      |             |
926      // |      |             |
927      // |      11--10    15--14
928      // |      |   |     |   |
929      // |1.0   8---9----12--13
930      // v----(-1.0)x---------1.0-x>
931      //
932      (
933          [
934              // sides
935              tvert(-w, h - y, -1.0, v),      // 11
936              tvert(-w + x, h - y, 1.0, v),   // 10
937              tvert(-w + x, -h + y, 1.0, -v), // 1
938              tvert(-w, -h + y, -1.0, -v),    // 0
939              tvert(-w + x, -h, -1.0, u),     // 2
940              tvert(-w + x, -h + y, 1.0, u),  // 1
941              tvert(w - x, -h + y, 1.0, -u),  // 4
942              tvert(w - x, -h, -1.0, -u),     // 7
943              tvert(w, -h + y, -1.0, v),      // 5
944              tvert(w - x, -h + y, 1.0, v),   // 4
945              tvert(w - x, h - y, 1.0, -v),   // 15
946              tvert(w, h - y, -1.0, -v),      // 14
947              tvert(w - x, h, -1.0, u),       // 12
948              tvert(w - x, h - y, 1.0, u),    // 15
949              tvert(-w + x, h - y, 1.0, -u),  // 10
950              tvert(-w + x, h, -1.0, -u),     // 9
951              // corners
952              tvert(-w, -h + y, -1.0, 1.0),    // 0
953              tvert(-w + x, -h + y, 1.0, 1.0), // 1
954              tvert(-w + x, -h, 1.0, -1.0),    // 2
955              tvert(-w, -h, -1.0, -1.0),       // 3
956              tvert(w - x, -h + y, 1.0, 1.0),  // 4
957              tvert(w, -h + y, 1.0, -1.0),     // 5
958              tvert(w, -h, -1.0, -1.0),        // 6
959              tvert(w - x, -h, -1.0, 1.0),     // 7
960              tvert(-w, h, -1.0, -1.0),        // 8
961              tvert(-w + x, h, -1.0, 1.0),     // 9
962              tvert(-w + x, h - y, 1.0, 1.0),  // 10
963              tvert(-w, h - y, 1.0, -1.0),     // 11
964              tvert(w - x, h, 1.0, -1.0),      // 12
965              tvert(w, h, -1.0, -1.0),         // 13
966              tvert(w, h - y, -1.0, 1.0),      // 14
967              tvert(w - x, h - y, 1.0, 1.0),   // 15
968              // center
969              tvert(-w + x, h - y, -u, v),   // 10
970              tvert(w - x, h - y, u, v),     // 15
971              tvert(w - x, -h + y, u, -v),   // 4
972              tvert(-w + x, -h + y, -u, -v), // 1
973          ],
974          [
975              0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14,
976              15, // corners
977              16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23, 24, 25, 26, 24, 26, 27, 28, 29, 30, 28,
978              30, 31, // sides
979              32, 33, 34, 32, 34, 35, // middle
980          ],
981      )
982  }