1use crate::{
2 material::Material,
3 maths::{Matrix, Plane, Pose, Quat, Vec2, Vec3, lerp, units::CM},
4 mesh::{Inds, Mesh, Vertex},
5 prelude::*,
6 sound::Sound,
7 system::{
8 Align, Backend, BackendXRType, FingerId, Hand, Handed, Hierarchy, Input, JointId, Key, Lines, Text, TextStyle,
9 },
10 tex::Tex,
11 ui::{Ui, UiColor},
12 util::{
13 Color128, Time,
14 named_colors::{GREEN, WHITE},
15 },
16};
17use std::{borrow::BorrowMut, collections::VecDeque};
18
19pub struct HandMenuItem {
24 pub name: String,
25 pub image: Option<Material>,
26 pub action: RefCell<HandMenuAction>,
27 pub callback: RefCell<Box<dyn FnMut()>>,
28}
29
30impl HandMenuItem {
31 pub fn new<C: FnMut() + 'static>(
39 name: impl AsRef<str>,
40 image: Option<Material>,
41 callback: C,
42 action: HandMenuAction,
43 ) -> Self {
44 Self {
45 name: name.as_ref().to_owned(),
46 image,
47 callback: RefCell::new(Box::new(callback)),
48 action: RefCell::<HandMenuAction>::new(action),
49 }
50 }
51
52 pub fn draw_basic(&self, token: &MainThreadToken, at: Vec3, focused: bool) {
58 let scale = match focused {
59 true => Vec3::ONE * 0.6,
60 false => Vec3::ONE * 0.5,
61 };
62 Text::add_at(
63 token,
64 &self.name,
65 Matrix::t_s(at, scale),
66 None,
67 None,
68 None,
69 Some(Align::BottomCenter),
70 None,
71 None,
72 None,
73 );
74 }
75}
76
77pub enum HandRadial {
81 Item(HandMenuItem),
82 Layer(HandRadialLayer),
83}
84
85impl HandRadial {
86 pub fn item<C: FnMut() + 'static>(
93 name: impl AsRef<str>,
94 image: Option<Material>,
95 callback: C,
96 action: HandMenuAction,
97 ) -> Self {
98 Self::Item(HandMenuItem::new(name, image, callback, action))
99 }
100
101 pub fn layer(
109 name: impl AsRef<str>,
110 image: Option<Material>,
111 start_angle: Option<f32>,
112 items: Vec<HandRadial>,
113 ) -> Self {
114 Self::Layer(HandRadialLayer::new(name, image, start_angle, items))
115 }
116
117 pub fn items_count(&self) -> usize {
119 match self {
120 HandRadial::Item(_) => 0,
121 HandRadial::Layer(layer) => layer.items.len(),
122 }
123 }
124
125 pub fn items(&self) -> &Vec<Rc<HandRadial>> {
127 match self {
128 HandRadial::Item(_) => panic!("Cannot get items from an item"),
129 HandRadial::Layer(layer) => &layer.items,
130 }
131 }
132
133 pub fn is_back_action(&self) -> bool {
135 match self {
136 HandRadial::Item(item) => {
137 let value = item.action.borrow();
138 *value == HandMenuAction::Back
139 }
140 HandRadial::Layer(_) => false,
141 }
142 }
143
144 pub fn is_checked_action(&self) -> Option<u8> {
146 match self {
147 HandRadial::Item(item) => {
148 let value = item.action.borrow();
149 if let HandMenuAction::Checked(group) = *value { Some(group) } else { None }
150 }
151 HandRadial::Layer(_) => None,
152 }
153 }
154
155 pub fn is_unchecked_action(&self) -> Option<u8> {
157 match self {
158 HandRadial::Item(item) => {
159 let value = item.action.borrow();
160 if let HandMenuAction::Unchecked(group) = *value { Some(group) } else { None }
161 }
162 HandRadial::Layer(_) => None,
163 }
164 }
165
166 pub fn get_start_angle(&self) -> f32 {
168 match self {
169 HandRadial::Item(_) => 0.0,
170 HandRadial::Layer(layer) => layer.start_angle,
171 }
172 }
173
174 pub fn get_back_angle(&self) -> f32 {
176 match self {
177 HandRadial::Item(_) => 0.0,
178 HandRadial::Layer(layer) => layer.back_angle,
179 }
180 }
181
182 pub fn get_name(&self) -> &str {
184 match self {
185 HandRadial::Item(item) => &item.name,
186 HandRadial::Layer(layer) => &layer.layer_name,
187 }
188 }
189}
190
191pub struct HandRadialLayer {
197 pub layer_name: String,
198 pub items: Vec<Rc<HandRadial>>,
199 pub start_angle: f32,
200 pub back_angle: f32,
201 parent: Option<String>,
202 layer_item: HandMenuItem,
203}
204
205impl HandRadialLayer {
215 pub fn new(
216 name: impl AsRef<str>,
217 image: Option<Material>,
218 start_angle_opt: Option<f32>,
219 items_in: Vec<HandRadial>,
220 ) -> Self {
221 let name = name.as_ref().to_owned();
222 let mut items = vec![];
223 for item in items_in {
224 items.push(Rc::new(item));
225 }
226
227 let mut back_angle = 0.0;
228 let mut start_angle = 0.0;
229 match start_angle_opt {
230 Some(value) => start_angle = value,
231 None => {
232 let mut i = 0.0;
233 for item in items.iter() {
234 if item.is_back_action() {
235 let step = 360.0 / (items.len() as f32);
236 back_angle = (i + 0.5) * step;
237 }
238 i += 1.0;
239 }
240 }
241 }
242
243 Self {
244 layer_name: name.clone(),
245 items,
246 start_angle,
247 back_angle,
248 parent: None,
249 layer_item: HandMenuItem::new(name.clone(), image, || {}, HandMenuAction::Callback),
250 }
251 }
252
253 pub fn add_child(&mut self, mut layer: HandRadialLayer) -> &mut Self {
256 layer.parent = Some(self.layer_name.clone());
257 self.items.push(Rc::new(HandRadial::Layer(layer)));
258 self
259 }
260
261 pub fn find_child(&self, name: impl AsRef<str>) -> Option<&HandRadialLayer> {
264 for line in self.items.iter() {
265 let line = line.as_ref();
266 match line {
267 HandRadial::Layer(s) => {
268 if s.layer_name.eq(&name.as_ref().to_string()) {
269 return Some(s);
270 } else if let Some(sub_s) = s.find_child(&name) {
271 return Some(sub_s);
272 };
273 }
274 HandRadial::Item(_) => {}
275 }
276 }
277
278 None
279 }
280
281 pub fn remove_child(&mut self, name: impl AsRef<str>) -> bool {
285 for (index, line) in self.items.iter().enumerate() {
286 let line = line.as_ref();
287 match line {
288 HandRadial::Layer(s) => {
289 if s.layer_name.eq(&name.as_ref().to_string()) {
290 self.items.remove(index);
291 return true;
292 }
293 }
294 HandRadial::Item(_) => {}
295 }
296 }
297
298 false
299 }
300
301 pub fn add_item(&mut self, menu_item: HandMenuItem) -> &mut Self {
304 self.items.push(Rc::new(HandRadial::Item(menu_item)));
305 self
306 }
307
308 pub fn find_item(&mut self, name: impl AsRef<str>) -> Option<&HandMenuItem> {
311 for line in self.items.iter() {
312 let line = line.as_ref();
313 match line {
314 HandRadial::Item(s) => {
315 if s.name.eq(name.as_ref()) {
316 return Some(s);
317 }
318 }
319 HandRadial::Layer(_) => {}
320 }
321 }
322
323 None
324 }
325
326 pub fn remove_item(&mut self, name: impl AsRef<str>) -> bool {
329 for (index, line) in self.items.iter().enumerate() {
330 let line = line.as_ref();
331 match line {
332 HandRadial::Item(s) => {
333 if s.name.eq(name.as_ref()) {
334 self.items.remove(index);
335 return true;
336 }
337 }
338 HandRadial::Layer(_) => {}
339 }
340 }
341
342 false
343 }
344}
345
346#[derive(Copy, Clone, Debug, PartialEq)]
353pub enum HandMenuAction {
354 Callback,
356 Back,
358 Close,
360 Checked(u8),
363 Unchecked(u8),
366}
367
368pub const HAND_MENU_RADIAL: &str = "hand_menu_radial_";
370pub const HAND_MENU_RADIAL_FOCUS: &str = "hand_menu_radial_focus";
372
373#[derive(IStepper)]
430pub struct HandMenuRadial {
431 id: StepperId,
432 sk_info: Option<Rc<RefCell<SkInfo>>>,
433 enabled: bool,
434
435 menu_stack: Vec<String>,
436 menu_pose: Pose,
437 dest_pose: Pose,
438 root: Rc<HandRadial>,
439 active_layer: Rc<HandRadial>,
440 last_selected: Rc<HandRadial>,
441 last_selected_time: f32,
442 nav_stack: VecDeque<Rc<HandRadial>>,
443 active_hand: Handed,
444 activation: f32,
445 menu_scale: f32,
446 angle_offset: f32,
447
448 background: Mesh,
449 background_edge: Mesh,
450 activation_button: Mesh,
451 activation_hamburger: Mesh,
452 activation_ring: Mesh,
453 child_indicator: Mesh,
454 img_frame: Mesh,
455 pub checked_material: Material,
456 pub on_checked_material: Material,
457 pub text_style: TextStyle,
458}
459
460unsafe impl Send for HandMenuRadial {}
461
462impl HandMenuRadial {
463 pub fn build_id(id: &str) -> String {
464 format!("{HAND_MENU_RADIAL}{id}")
465 }
466
467 fn start(&mut self) -> bool {
470 true
471 }
472
473 fn check_event(&mut self, id: &StepperId, key: &str, value: &str) {
475 if key == HAND_MENU_RADIAL_FOCUS {
476 if value.parse().unwrap_or_default() {
477 if *id == self.id {
478 self.enabled = true
479 } else if self.enabled {
480 self.menu_stack.push(id.clone());
481 self.enabled = false;
482 }
483 } else if *id == self.id {
484 self.enabled = false
485 } else {
486 if let Some(index) = self.menu_stack.iter().position(|x| *x == *id) {
487 self.menu_stack.remove(index);
488 }
489 if self.menu_stack.is_empty() {
490 self.enabled = true;
491 }
492 }
493 }
494 }
495
496 fn draw(&mut self, token: &MainThreadToken) {
499 if self.active_hand == Handed::Max {
500 for hand in [Handed::Left, Handed::Right] {
501 self.step_menu_indicator(token, hand);
502 }
503 } else {
504 self.step_menu(token, Input::hand(self.active_hand));
505 }
506 }
507
508 pub const SIMULATOR_KEY: Key = Key::F1;
511 pub const MIN_DIST: f32 = 0.03;
512 pub const MID_DIST: f32 = 0.065;
513 pub const MAX_DIST: f32 = 0.1;
514 pub const MIN_SCALE: f32 = 0.05;
515 pub const SLICE_GAP: f32 = 0.002;
516 pub const OUT_OF_VIEW_ANGLE: f32 = 0.866;
517 pub const ACTIVATION_ANGLE: f32 = 0.978;
518
519 pub fn new(root_layer: HandRadialLayer) -> Self {
523 let root = Rc::new(HandRadial::Layer(root_layer));
524 let active_layer = root.clone();
525 let last_selected = root.clone();
526 let last_selected_time = Time::get_total_unscaledf();
527 let activation_btn_radius = 1.0 * CM;
528 let activation_button = generate_activation_button(activation_btn_radius);
529 let activation_hamburger = generate_activation_hamburger(activation_btn_radius);
530 let mut activation_ring = Mesh::new();
531 generate_slice_mesh(360.0, activation_btn_radius, activation_btn_radius + 0.005, 0.0, &mut activation_ring);
532 let child_indicator = generate_child_indicator(Self::MAX_DIST - 0.008, 0.004);
533 let img_frame = generate_img_frame(Self::MIN_DIST + 0.013, 0.012);
534 let tex_checked = Tex::from_file("icons/radio.png", true, None).unwrap_or_default();
535 let mut checked_material = Material::pbr_clip().copy();
536 checked_material.diffuse_tex(tex_checked).clip_cutoff(0.1);
537 let tex_on_checked = Tex::from_file("icons/checked.png", true, None).unwrap_or_default();
538 let mut on_checked_material = Material::pbr_clip().copy();
539 on_checked_material.diffuse_tex(tex_on_checked).clip_cutoff(0.1).color_tint(GREEN);
540 let mut text_style = TextStyle::default();
541 text_style.layout_height(0.016);
542 Self {
543 id: "HandleMenuRadial_not_initialized".to_string(),
544 sk_info: None,
545 enabled: false,
546
547 menu_stack: Vec::new(),
548 menu_pose: Pose::default(),
549 dest_pose: Pose::default(),
550 root,
551 active_layer,
552 last_selected,
553 last_selected_time,
554 nav_stack: VecDeque::new(),
555 active_hand: Handed::Max,
556 activation: 0.0,
557 menu_scale: 0.0,
558 angle_offset: 0.0,
559 background: Mesh::new(),
560 background_edge: Mesh::new(),
561 activation_button,
562 activation_hamburger,
563 activation_ring,
564 child_indicator,
565 img_frame,
566 text_style,
567 checked_material,
568 on_checked_material,
569 }
570 }
571
572 pub fn show(&mut self, at: impl Into<Vec3>, hand: Handed) {
576 if self.active_hand != Handed::Max {
577 self.close();
578 }
579 let at_pos = &at.into();
580 Sound::click().play(*at_pos, None);
581 self.dest_pose.position = *at_pos;
582 self.dest_pose.orientation = Quat::look_at(*at_pos, Input::get_head().position, None);
583 Log::diag(format!("dest_pose at show{}", self.dest_pose));
584 self.active_layer = self.root.clone();
585 self.active_hand = hand;
586
587 generate_slice_mesh(
588 360.0 / (self.root.items_count() as f32),
589 Self::MIN_DIST,
590 Self::MAX_DIST,
591 Self::SLICE_GAP,
592 &mut self.background,
593 );
594 generate_slice_mesh(
595 360.0 / (self.root.items_count() as f32),
596 Self::MAX_DIST,
597 Self::MAX_DIST + 0.005,
598 Self::SLICE_GAP,
599 &mut self.background_edge,
600 );
601 }
602
603 pub fn close(&mut self) {
606 if self.active_hand != Handed::Max {
607 Sound::unclick().play(self.menu_pose.position, None);
608 self.menu_scale = Self::MIN_SCALE;
609 self.active_hand = Handed::Max;
610 self.angle_offset = 0.0;
611 self.nav_stack.clear();
612 }
613 }
614
615 fn step_menu_indicator(&mut self, token: &MainThreadToken, handed: Handed) {
616 let hand = Input::hand(handed);
617 if !hand.is_tracked() {
618 return;
619 };
620 let mut show_menu = false;
621 if Backend::xr_type() == BackendXRType::Simulator {
622 if Input::key(Self::SIMULATOR_KEY).is_just_active() {
623 show_menu = true
624 }
625 } else if (Input::get_controller_menu_button().is_just_active()) && handed == Handed::Left {
626 show_menu = true;
627 }
628 if show_menu {
629 self.menu_pose = hand.palm;
630 let mut at = hand.get(FingerId::Index, JointId::Tip).position;
631 if at == Vec3::ZERO {
632 self.menu_pose = Input::controller(handed).aim;
633 at = self.menu_pose.position
634 }
635 self.show(at, handed);
636 return;
637 }
638
639 let head_fwd = Input::get_head().get_forward();
640 let at = hand.palm.position;
641 if at == Vec3::ZERO {
642 return;
644 }
645 let hand_dir = (at - Input::get_head().position).get_normalized();
646
647 let in_view = Vec3::dot(head_fwd, hand_dir) > Self::OUT_OF_VIEW_ANGLE;
648
649 if !in_view {
650 return;
651 }
652
653 let palm_direction = hand.palm.get_forward();
654 self.menu_pose = hand.palm;
655
656 let direction_to_head = -hand_dir;
657 let facing = Vec3::dot(palm_direction, direction_to_head);
658
659 if facing < 0.0 {
660 return;
661 }
662
663 let color_primary = Ui::get_theme_color(UiColor::Primary, None).to_linear();
664 let color_common = Ui::get_theme_color(UiColor::Background, None).to_linear();
665 let color_text = Ui::get_theme_color(UiColor::Text, None).to_linear();
666
667 self.menu_pose.position += (1.0 - hand.grip_activation) * palm_direction * CM * 4.5;
668 self.activation_button.draw(
669 token,
670 Material::ui(),
671 self.menu_pose.to_matrix(None),
672 Some(Color128::lerp(
673 color_common,
674 color_primary,
675 ((facing - (Self::ACTIVATION_ANGLE - 0.01)) / 0.001).clamp(0.0, 1.0),
676 )),
677 None,
678 );
679 self.activation_hamburger
680 .draw(token, Material::ui(), self.menu_pose.to_matrix(None), Some(color_text), None);
681 self.menu_pose.position += (1.0 - hand.grip_activation) * palm_direction * CM * 2.0;
682 self.activation_ring
683 .draw(token, Material::ui(), self.menu_pose.to_matrix(None), Some(color_primary), None);
684
685 if facing < Self::ACTIVATION_ANGLE {
686 return;
687 }
688
689 if hand.is_just_gripped() {
690 let mut at = hand.get(FingerId::Index, JointId::Tip).position;
691 if at == Vec3::ZERO {
692 at = Input::controller(hand.handed).aim.position
693 }
694 self.show(at, handed);
695 }
696 }
697
698 fn step_menu(&mut self, token: &MainThreadToken, hand: Hand) {
699 let time = f32::min(1.0, Time::get_step_unscaledf() * 24.0);
701 self.menu_pose.position = Vec3::lerp(self.menu_pose.position, self.dest_pose.position, time);
702 self.menu_pose.orientation = Quat::slerp(self.menu_pose.orientation, self.dest_pose.orientation, time);
703 self.activation = lerp(self.activation, 1.0, time);
704 self.menu_scale = lerp(self.menu_scale, 1.0, time);
705
706 let layer = self.active_layer.as_ref();
708 let count = layer.items_count();
709 let step = 360.0 / (count as f32);
710 let half_step = step / 2.0;
711
712 Hierarchy::push(token, self.menu_pose.to_matrix(Some(self.menu_scale * Vec3::ONE)), None);
715
716 let mut tip_world = hand.get(FingerId::Index, JointId::Tip).position;
718 if tip_world == Vec3::ZERO {
719 tip_world = Input::controller(hand.handed).aim.position
720 }
721 let tip_local = self.dest_pose.to_matrix(None).get_inverse().transform_point(tip_world);
722 let mag_sq = tip_local.magnitude_squared();
723 let on_menu = tip_local.z > -0.02 && tip_local.z < 0.02;
724 let focused = on_menu && mag_sq > Self::MIN_DIST.powi(2);
725 let selected = on_menu && mag_sq > Self::MID_DIST.powi(2);
726 let cancel = mag_sq > Self::MAX_DIST.powi(2);
727 let mut finger_angle =
731 tip_local.y.atan2(tip_local.x).to_degrees() - (layer.get_start_angle() + self.angle_offset);
732 while finger_angle < 0.0 {
733 finger_angle += 360.0;
734 }
735 let angle_id = (finger_angle / step).trunc() as usize;
736 Lines::add(token, Vec3::new(0.0, 0.0, -0.008), Vec3::new(tip_local.x, tip_local.y, -0.008), WHITE, None, 0.006);
737
738 let color_primary = Ui::get_theme_color(UiColor::Primary, None).to_linear();
740 let color_common = Ui::get_theme_color(UiColor::Background, None).to_linear();
741 for (i, line) in layer.items().iter().enumerate() {
742 let curr_angle = (i as f32) * step + layer.get_start_angle() + self.angle_offset;
743 let highlight = focused && angle_id == i && self.activation >= 0.99;
744 let depth = if highlight { -0.005 } else { 0.0 };
745 let mut at = Vec3::angle_xy(curr_angle + half_step, 0.0) * Self::MID_DIST;
746 at.z = depth;
747
748 let r = Matrix::t_r(Vec3::new(0.0, 0.0, depth), Quat::from_angles(0.0, 0.0, curr_angle));
749 self.background.draw(
750 token,
751 Material::ui(),
752 r,
753 Some(color_common * (if highlight { 2.0 } else { 1.0 })),
754 None,
755 );
756 self.background_edge.draw(
757 token,
758 Material::ui(),
759 r,
760 Some(color_primary * (if highlight { 2.0 } else { 1.0 })),
761 None,
762 );
763 let mut add_offset = 1.0;
764 let item_to_draw: &HandMenuItem;
765
766 match line.as_ref() {
767 HandRadial::Item(item) => {
768 item_to_draw = item;
769 match *item.action.borrow() {
770 HandMenuAction::Back => self.child_indicator.draw(
771 token,
772 Material::ui(),
773 Matrix::t_r(
774 Vec3::new(0.0, 0.0, depth),
775 Quat::from_angles(0.0, 0.0, curr_angle + half_step),
776 ),
777 None,
778 None,
779 ),
780 HandMenuAction::Close => (),
781 HandMenuAction::Callback => (),
782 HandMenuAction::Checked(_group) => {
783 let checked_material = if item_to_draw.image.is_none() {
784 &self.checked_material
785 } else {
786 &self.on_checked_material
787 };
788 self.img_frame.draw(
789 token,
790 checked_material,
791 Matrix::t_r(
792 Vec3::new(0.0, 0.0, depth - 0.01),
793 Quat::from_angles(0.0, 0.0, curr_angle + half_step),
794 ),
795 None,
796 None,
797 );
798 add_offset = 1.2;
799 }
800 HandMenuAction::Unchecked(_group) => (),
801 };
802 }
803 HandRadial::Layer(layer) => {
804 item_to_draw = &layer.layer_item;
805 self.child_indicator.draw(
806 token,
807 Material::ui(),
808 Matrix::t_r(Vec3::new(0.0, 0.0, depth), Quat::from_angles(0.0, 0.0, curr_angle + half_step)),
809 None,
810 None,
811 );
812 }
813 };
814 if let Some(image_material) = &item_to_draw.image {
815 self.img_frame.draw(
816 token,
817 image_material,
818 Matrix::t_r(Vec3::new(0.0, 0.0, depth), Quat::from_angles(0.0, 0.0, curr_angle + half_step)),
819 None,
820 None,
821 );
822 add_offset = 1.2;
823 }
824
825 Ui::push_text_style(self.text_style);
826 item_to_draw.draw_basic(token, at * add_offset, highlight);
827 Ui::pop_text_style();
828 }
829 Hierarchy::pop(token);
831
832 if self.activation < 0.99 {
833 return;
834 }
835 if selected {
836 if let Some(item_selected) = layer.items().get(angle_id) {
837 if Rc::ptr_eq(item_selected, &self.last_selected)
838 && Time::get_total_unscaledf() - self.last_selected_time < 1.5
839 {
840 return;
841 };
842 self.last_selected = item_selected.clone();
843 self.last_selected_time = Time::get_total_unscaledf();
844
845 if let Some(group_to_change) = item_selected.as_ref().is_unchecked_action() {
846 for line in layer.items().iter() {
847 if let Some(group) = line.as_ref().is_checked_action()
848 && group == group_to_change
849 {
850 let mut to_reverse = line.as_ref();
851 let to_to_reverse = to_reverse.borrow_mut();
852
853 if let HandRadial::Item(menu_item) = to_to_reverse {
854 menu_item.action.replace(HandMenuAction::Unchecked(group));
855 }
856 }
857 }
858 let mut to_reverse = item_selected.as_ref();
859 let to_to_reverse = to_reverse.borrow_mut();
860 if let HandRadial::Item(menu_item) = to_to_reverse {
861 menu_item.action.replace(HandMenuAction::Checked(group_to_change));
862 }
863 } else if let Some(group_to_change) = item_selected.as_ref().is_checked_action() {
864 let mut cpt = 0;
866 for line in layer.items().iter() {
867 if let Some(group) = line.as_ref().is_checked_action() {
868 if group_to_change == group {
869 cpt += 1
870 }
871 } else if let Some(group) = line.as_ref().is_unchecked_action()
872 && group_to_change == group
873 {
874 cpt += 1
875 }
876 }
877 if cpt == 1 {
878 let mut to_reverse = item_selected.as_ref();
879 let to_to_reverse = to_reverse.borrow_mut();
880 if let HandRadial::Item(menu_item) = to_to_reverse {
881 menu_item.action.replace(HandMenuAction::Unchecked(group_to_change));
882 }
883 }
884 }
885
886 self.select_item(item_selected.clone(), tip_world, ((angle_id as f32) + 0.5) * step)
887 } else {
888 Log::err(format!("HandMenuRadial : Placement error for index {angle_id}"));
889 }
890 }
891 if cancel {
892 self.close()
893 };
894 let mut close_menu = false;
895 if Backend::xr_type() == BackendXRType::Simulator {
896 if Input::key(Self::SIMULATOR_KEY).is_just_active() {
897 close_menu = true
898 }
899 } else if Input::get_controller_menu_button().is_just_active() {
900 close_menu = true;
901 }
902 if close_menu {
903 self.close()
904 }
905 }
906
907 fn select_layer(&mut self, new_layer_rc: Rc<HandRadial>) {
908 let new_layer = match new_layer_rc.as_ref() {
909 HandRadial::Item(_) => {
910 Log::err("HandMenuRadial : Item is not a valid layer");
911 return;
912 }
913 HandRadial::Layer(layer) => layer,
914 };
915 Sound::click().play(self.menu_pose.position, None);
916 self.nav_stack.push_back(self.active_layer.clone());
917 self.active_layer = new_layer_rc.clone();
918 let divisor = new_layer.items.len() as f32;
919 generate_slice_mesh(360.0 / divisor, Self::MIN_DIST, Self::MAX_DIST, Self::SLICE_GAP, &mut self.background);
920 generate_slice_mesh(
921 360.0 / divisor,
922 Self::MAX_DIST,
923 Self::MAX_DIST + 0.005,
924 Self::SLICE_GAP,
925 &mut self.background_edge,
926 );
927 Log::diag(format!("HandRadialMenu : Layer {} opened", new_layer.layer_name));
928 }
929
930 fn back(&mut self) {
931 Sound::unclick().play(self.menu_pose.position, None);
932 if let Some(prev_layer) = self.nav_stack.pop_back() {
933 self.active_layer = prev_layer.clone();
934 } else {
935 Log::err("HandMenuRadial : No back layer !!")
936 }
937 let divisor = self.active_layer.items_count() as f32;
938 generate_slice_mesh(360.0 / divisor, Self::MIN_DIST, Self::MAX_DIST, Self::SLICE_GAP, &mut self.background);
939 generate_slice_mesh(
940 360.0 / divisor,
941 Self::MAX_DIST,
942 Self::MAX_DIST + 0.005,
943 Self::SLICE_GAP,
944 &mut self.background_edge,
945 );
946 }
947
948 fn select_item(&mut self, line: Rc<HandRadial>, at: Vec3, from_angle: f32) {
949 match line.as_ref() {
950 HandRadial::Item(item) => {
951 match *item.action.borrow() {
952 HandMenuAction::Close => self.close(),
953 HandMenuAction::Callback => {}
954 HandMenuAction::Checked(_) => {}
955 HandMenuAction::Unchecked(_) => {}
956 HandMenuAction::Back => {
957 self.back();
958 self.reposition(at, from_angle)
959 }
960 };
961 let mut callback = item.callback.borrow_mut();
962 callback()
963 }
964 HandRadial::Layer(layer) => {
965 Log::diag(format!("HandRadialMenu : open Layer {}", layer.layer_name));
966 self.select_layer(line.clone());
967 self.reposition(at, from_angle)
968 }
969 }
970 }
971
972 fn reposition(&mut self, at: Vec3, from_angle: f32) {
973 let plane = Plane::from_point(self.menu_pose.position, self.menu_pose.get_forward());
974 self.dest_pose.position = plane.closest(at);
975
976 self.activation = 0.0;
977
978 if self.active_layer.get_back_angle() != 0.0 {
979 self.angle_offset = (from_angle - self.active_layer.get_back_angle()) + 180.0;
980 while self.angle_offset < 0.0 {
981 self.angle_offset += 360.0;
982 }
983 while self.angle_offset > 360.0 {
984 self.angle_offset -= 360.0;
985 }
986 } else {
987 self.angle_offset = 0.0
988 };
989 }
990}
991
992fn generate_slice_mesh(angle: f32, min_dist: f32, max_dist: f32, gap: f32, mesh: &mut Mesh) {
993 let count = angle * 0.25;
994
995 let inner_start_angle = gap / min_dist.to_radians();
996 let inner_angle = angle - inner_start_angle * 2.0;
997 let inner_step = inner_angle / (count - 1.0);
998
999 let outer_start_angle = gap / max_dist.to_radians();
1000 let outer_angle = angle - outer_start_angle * 2.0;
1001 let outer_step = outer_angle / (count - 1.0);
1002
1003 let mut verts: Vec<Vertex> = vec![];
1004 let mut inds: Vec<Inds> = vec![];
1005
1006 let icount = count as u32;
1007 for i in 0..icount {
1008 let inner_dir = Vec3::angle_xy(inner_start_angle + (i as f32) * inner_step, 0.005);
1009 let outer_dir = Vec3::angle_xy(outer_start_angle + (i as f32) * outer_step, 0.005);
1010 verts.push(Vertex::new(inner_dir * min_dist, Vec3::FORWARD, None, None));
1011 verts.push(Vertex::new(outer_dir * max_dist, Vec3::FORWARD, None, None));
1012
1013 if i != icount - 1 {
1014 inds.push((i + 1) * 2 + 1);
1015 inds.push(i * 2 + 1);
1016 inds.push(i * 2);
1017
1018 inds.push((i + 1) * 2);
1019 inds.push((i + 1) * 2 + 1);
1020 inds.push(i * 2);
1021 }
1022 }
1023
1024 mesh.set_verts(verts.as_slice(), true);
1025 mesh.set_inds(inds.as_slice());
1026}
1027
1028fn generate_activation_button(radius: f32) -> Mesh {
1029 let spokes = 36;
1030 let mut verts: Vec<Vertex> = vec![];
1031 let mut inds: Vec<Inds> = vec![];
1032
1033 for i in 0..spokes {
1034 verts.push(Vertex::new(
1035 Vec3::angle_xy((i as f32) * (360.0 / (spokes as f32)) * radius, 0.0),
1036 Vec3::FORWARD,
1037 None,
1038 None,
1039 ))
1040 }
1041
1042 for i in 0..(spokes - 2) {
1043 let half = i / 2;
1044
1045 if i % 2 == 0 {
1046 inds.push(spokes - 1 - half);
1047 inds.push(half + 1);
1048 inds.push((spokes - half) % spokes);
1049 } else {
1050 inds.push(half + 1);
1051 inds.push(spokes - (half + 1));
1052 inds.push(half + 2);
1053 }
1054 }
1055
1056 let mut mesh = Mesh::new();
1057
1058 mesh.set_inds(inds.as_slice());
1059 mesh.set_verts(verts.as_slice(), true);
1060 mesh
1061}
1062
1063fn generate_activation_hamburger(radius: f32) -> Mesh {
1064 let mut verts: Vec<Vertex> = vec![];
1065 let mut inds: Vec<Inds> = vec![];
1066
1067 let w = radius / 3.0;
1068 let h = radius / 16.0;
1069 let z = -0.003;
1070
1071 for i in 0..3 {
1072 let y = -radius / 3.0 + (i as f32) * radius / 3.0;
1073
1074 let a = i * 4;
1075 let b = i * 4 + 1;
1076 let c = i * 4 + 2;
1077 let d = i * 4 + 3;
1078
1079 verts.push(Vertex::new(Vec3::new(-w, y - h, z), Vec3::FORWARD, None, None));
1080 verts.push(Vertex::new(Vec3::new(w, y - h, z), Vec3::FORWARD, None, None));
1081 verts.push(Vertex::new(Vec3::new(w, y + h, z), Vec3::FORWARD, None, None));
1082 verts.push(Vertex::new(Vec3::new(-w, y + h, z), Vec3::FORWARD, None, None));
1083
1084 inds.push(c);
1085 inds.push(b);
1086 inds.push(a);
1087
1088 inds.push(d);
1089 inds.push(c);
1090 inds.push(a);
1091 }
1092
1093 let mut mesh = Mesh::new();
1094 mesh.set_inds(inds.as_slice());
1095 mesh.set_verts(verts.as_slice(), true);
1096
1097 mesh
1098}
1099
1100fn generate_child_indicator(distance: f32, radius: f32) -> Mesh {
1101 let mut verts: Vec<Vertex> = vec![];
1102 let mut inds: Vec<Inds> = vec![];
1103
1104 verts.push(Vertex::new(Vec3::new(distance, radius * 2.0, 0.0), Vec3::FORWARD, None, None));
1105 verts.push(Vertex::new(Vec3::new(distance + radius, 0.0, 0.0), Vec3::FORWARD, None, None));
1106 verts.push(Vertex::new(Vec3::new(distance, -radius * 2.0, 0.0), Vec3::FORWARD, None, None));
1107
1108 inds.push(0);
1109 inds.push(1);
1110 inds.push(2);
1111
1112 let mut mesh = Mesh::new();
1113 mesh.set_inds(inds.as_slice());
1114 mesh.set_verts(verts.as_slice(), true);
1115
1116 mesh
1117}
1118
1119fn generate_img_frame(distance: f32, radius: f32) -> Mesh {
1120 let mut verts: Vec<Vertex> = vec![];
1121 let mut inds: Vec<Inds> = vec![];
1122
1123 verts.push(Vertex::new(
1124 Vec3::new(distance + radius, -radius, 0.0),
1125 Vec3::FORWARD,
1126 Some(Vec2::new(0.0, 0.0)),
1127 None,
1128 ));
1129 verts.push(Vertex::new(
1130 Vec3::new(distance + radius, radius, 0.0),
1131 Vec3::FORWARD,
1132 Some(Vec2::new(1.0, 0.0)),
1133 None,
1134 ));
1135 verts.push(Vertex::new(
1136 Vec3::new(distance - radius, -radius, 0.0),
1137 Vec3::FORWARD,
1138 Some(Vec2::new(0.0, 1.0)),
1139 None,
1140 ));
1141 verts.push(Vertex::new(
1142 Vec3::new(distance - radius, radius, 0.0),
1143 Vec3::FORWARD,
1144 Some(Vec2::new(1.0, 1.0)),
1145 None,
1146 ));
1147
1148 inds.push(0);
1149 inds.push(2);
1150 inds.push(1);
1151 inds.push(1);
1152 inds.push(2);
1153 inds.push(3);
1154
1155 let mut mesh = Mesh::new();
1156 mesh.set_inds(inds.as_slice());
1157 mesh.set_verts(verts.as_slice(), true);
1158
1159 mesh
1160}