1use core::time::Duration;
21
22use bevy::{
23 camera::NormalizedRenderTarget,
24 input_focus::{
25 directional_navigation::{
26 AutoNavigationConfig, DirectionalNavigationMap, DirectionalNavigationPlugin,
27 },
28 FocusCause, InputFocus, InputFocusVisible,
29 },
30 math::{CompassOctant, Dir2},
31 picking::{
32 backend::HitData,
33 pointer::{Location, PointerId},
34 },
35 platform::collections::HashSet,
36 prelude::*,
37 ui::auto_directional_navigation::{AutoDirectionalNavigation, AutoDirectionalNavigator},
38};
39
40fn main() {
41 App::new()
42 .add_plugins((DefaultPlugins, DirectionalNavigationPlugin))
45 .insert_resource(InputFocusVisible(true))
48 .insert_resource(AutoNavigationConfig {
50 min_alignment_factor: 0.1,
52 max_search_distance: Some(200.0),
54 prefer_aligned: true,
56 })
57 .init_resource::<ActionState>()
58 .add_systems(Startup, setup_paged_ui)
63 .add_systems(PreUpdate, (process_inputs, navigate).chain())
65 .add_systems(
66 Update,
67 (
68 highlight_focused_element,
69 interact_with_focused_button,
70 reset_button_after_interaction,
71 update_focus_display
72 .run_if(|input_focus: Res<InputFocus>| input_focus.is_changed()),
73 update_key_display,
74 ),
75 )
76 .add_observer(universal_button_click_behavior)
77 .run();
78}
79
80const PAGE_1_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_400;
81const PAGE_1_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_500;
82const PAGE_1_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50;
83
84const PAGE_2_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::RED_400;
85const PAGE_2_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::RED_500;
86const PAGE_2_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::RED_50;
87
88const PAGE_3_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::GREEN_400;
89const PAGE_3_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::GREEN_500;
90const PAGE_3_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::GREEN_50;
91
92const NORMAL_BUTTON_COLORS: [Srgba; 3] = [
93 PAGE_1_NORMAL_BUTTON,
94 PAGE_2_NORMAL_BUTTON,
95 PAGE_3_NORMAL_BUTTON,
96];
97const PRESSED_BUTTON_COLORS: [Srgba; 3] = [
98 PAGE_1_PRESSED_BUTTON,
99 PAGE_2_PRESSED_BUTTON,
100 PAGE_3_PRESSED_BUTTON,
101];
102const FOCUSED_BORDER_COLORS: [Srgba; 3] = [
103 PAGE_1_FOCUSED_BORDER,
104 PAGE_2_FOCUSED_BORDER,
105 PAGE_3_FOCUSED_BORDER,
106];
107
108#[derive(Component)]
110struct FocusDisplay;
111
112#[derive(Component)]
114struct KeyDisplay;
115
116#[derive(Component)]
118struct Page(usize);
119
120fn universal_button_click_behavior(
122 mut click: On<Pointer<Click>>,
123 mut button_query: Query<(&mut BackgroundColor, &Page, &mut ResetTimer)>,
124) {
125 let button_entity = click.entity;
126 if let Ok((mut color, page, mut reset_timer)) = button_query.get_mut(button_entity) {
127 color.0 = PRESSED_BUTTON_COLORS[page.0].into();
128 reset_timer.0 = Timer::from_seconds(0.3, TimerMode::Once);
129 click.propagate(false);
130 }
131}
132
133#[derive(Component, Default, Deref, DerefMut)]
134struct ResetTimer(Timer);
135
136fn reset_button_after_interaction(
137 time: Res<Time>,
138 mut query: Query<(&mut ResetTimer, &mut BackgroundColor, &Page)>,
139) {
140 for (mut reset_timer, mut color, page) in query.iter_mut() {
141 reset_timer.tick(time.delta());
142 if reset_timer.just_finished() {
143 color.0 = NORMAL_BUTTON_COLORS[page.0].into();
144 }
145 }
146}
147
148fn setup_paged_ui(
161 mut commands: Commands,
162 mut manual_directional_nav_map: ResMut<DirectionalNavigationMap>,
163 mut input_focus: ResMut<InputFocus>,
164) {
165 commands.spawn(Camera2d);
166
167 let root_node = commands
169 .spawn(Node {
170 width: percent(100),
171 height: percent(100),
172 ..default()
173 })
174 .id();
175
176 let instructions = commands
178 .spawn((
179 Text::new(
180 "Directional Navigation Overrides Demo\n\n\
181 Use arrow keys or D-pad to navigate.\n\
182 Press Enter or A button to interact.\n\n\
183 Navigation on each page is a combination of \
184 both automatic and manual navigation.",
185 ),
186 Node {
187 position_type: PositionType::Absolute,
188 left: px(20),
189 top: px(20),
190 width: px(280),
191 padding: UiRect::all(px(12)),
192 border_radius: BorderRadius::all(px(8)),
193 ..default()
194 },
195 BackgroundColor(Color::srgba(0.1, 0.1, 0.1, 0.8)),
196 ))
197 .id();
198 commands.entity(root_node).add_children(&[instructions]);
199
200 commands.spawn((
202 Text::new("Focused: None"),
203 FocusDisplay,
204 Node {
205 position_type: PositionType::Absolute,
206 left: px(20),
207 bottom: px(80),
208 width: px(280),
209 padding: UiRect::all(px(12)),
210 border_radius: BorderRadius::all(px(8)),
211 ..default()
212 },
213 BackgroundColor(Color::srgba(0.1, 0.5, 0.1, 0.8)),
214 TextFont {
215 font_size: FontSize::Px(20.0),
216 ..default()
217 },
218 ));
219
220 commands.spawn((
222 Text::new("Last Key: None"),
223 KeyDisplay,
224 Node {
225 position_type: PositionType::Absolute,
226 left: px(20),
227 bottom: px(20),
228 width: px(280),
229 padding: UiRect::all(px(12)),
230 border_radius: BorderRadius::all(px(8)),
231 ..default()
232 },
233 BackgroundColor(Color::srgba(0.5, 0.1, 0.5, 0.8)),
234 TextFont {
235 font_size: FontSize::Px(20.0),
236 ..default()
237 },
238 ));
239
240 let mut pages_entities = [
242 Vec::with_capacity(12),
243 Vec::with_capacity(12),
244 Vec::with_capacity(12),
245 ];
246 let mut text_entities = Vec::with_capacity(10);
247 for (page_num, page_button_entities) in pages_entities.iter_mut().enumerate() {
248 if page_num == 1 {
249 setup_buttons_for_triangle_page(
251 &mut commands,
252 page_num,
253 (page_button_entities, &mut text_entities),
254 );
255 } else {
256 setup_buttons_for_grid_page(
258 &mut commands,
259 page_num,
260 (page_button_entities, &mut text_entities),
261 );
262 }
263
264 let visibility = if page_num == 0 {
266 Visibility::Visible
267 } else {
268 Visibility::Hidden
269 };
270 let page = commands
271 .spawn((
272 Node {
273 width: percent(100),
274 height: percent(100),
275 ..default()
276 },
277 visibility,
278 ))
279 .id();
280
281 commands
282 .entity(page)
283 .add_children(page_button_entities)
284 .add_children(&text_entities);
285
286 text_entities.clear();
287 }
288
289 let entity_pairs = [
291 ((0, 2), (1, 0)),
293 ((1, 2), (2, 0)),
295 ((2, 2), (3, 0)),
297 ];
298 for (page_num, page_entities) in pages_entities.iter().enumerate() {
299 if page_num == 1 {
301 continue;
302 }
303 for ((entity_a_row, entity_a_col), (entity_b_row, entity_b_col)) in entity_pairs.iter() {
304 manual_directional_nav_map.add_symmetrical_edge(
305 page_entities[entity_a_row * 3 + entity_a_col],
306 page_entities[entity_b_row * 3 + entity_b_col],
307 CompassOctant::East,
308 );
309 }
310 }
311
312 manual_directional_nav_map.add_symmetrical_edge(
317 pages_entities[1][2],
318 pages_entities[1][3],
319 CompassOctant::East,
320 );
321 manual_directional_nav_map.add_symmetrical_edge(
322 pages_entities[1][2],
323 pages_entities[1][3],
324 CompassOctant::South,
325 );
326 manual_directional_nav_map.add_symmetrical_edge(
327 pages_entities[1][2],
328 pages_entities[1][3],
329 CompassOctant::SouthEast,
330 );
331 for btn in &pages_entities[0] {
333 manual_directional_nav_map.block_edge(*btn, CompassOctant::South);
334 manual_directional_nav_map.block_edge(*btn, CompassOctant::North);
335 }
336
337 let mut col_entities = Vec::with_capacity(4);
339 for col in 0..=2 {
340 for row in 0..=3 {
341 col_entities.push(pages_entities[2][row * 3 + col]);
342 }
343 manual_directional_nav_map.add_looping_edges(&col_entities, CompassOctant::North);
344 col_entities.clear();
345 }
346
347 manual_directional_nav_map.add_symmetrical_edge(
351 pages_entities[0][11],
352 pages_entities[1][0],
353 CompassOctant::East,
354 );
355 manual_directional_nav_map.add_edge(
360 pages_entities[1][3],
361 pages_entities[2][0],
362 CompassOctant::South,
363 );
364 manual_directional_nav_map.add_edge(
367 pages_entities[2][0],
368 pages_entities[1][3],
369 CompassOctant::West,
370 );
371 manual_directional_nav_map.add_symmetrical_edge(
374 pages_entities[2][11],
375 pages_entities[0][0],
376 CompassOctant::East,
377 );
378
379 input_focus.set(pages_entities[0][0], FocusCause::Navigated);
381}
382
383fn setup_buttons_for_grid_page(
386 commands: &mut Commands,
387 page_num: usize,
388 entities: (&mut Vec<Entity>, &mut Vec<Entity>),
389) {
390 let (page_button_entities, text_entities) = entities;
391
392 let button_positions = [
395 [(450.0, 80.0), (650.0, 80.0), (850.0, 80.0)],
397 [(450.0, 215.0), (650.0, 215.0), (850.0, 215.0)],
399 [(450.0, 350.0), (650.0, 350.0), (850.0, 350.0)],
401 [(450.0, 485.0), (650.0, 485.0), (850.0, 485.0)],
403 ];
404 for (i, row) in button_positions.iter().enumerate() {
405 for (j, (left, top)) in row.iter().enumerate() {
406 let button_entity = spawn_auto_nav_button(
407 commands,
408 format!("Btn {}-{}", i + 1, j + 1),
409 left,
410 top,
411 page_num,
412 );
413 page_button_entities.push(button_entity);
414 }
415 }
416
417 let current_page_entity = spawn_small_text_node(
419 commands,
420 format!("Currently on Page {}", page_num + 1),
421 650,
422 20,
423 Justify::Center,
424 );
425 text_entities.push(current_page_entity);
426
427 let previous_page = if page_num == 0 { 3 } else { page_num };
429 let previous_page_entity = spawn_small_text_node(
430 commands,
431 format!("Page {} << ", previous_page),
432 310,
433 120,
434 Justify::Right,
435 );
436 text_entities.push(previous_page_entity);
437
438 let next_page_entity = spawn_small_text_node(
440 commands,
441 format!(">> Page {}", (page_num + 1) % 3 + 1),
442 1000,
443 525,
444 Justify::Left,
445 );
446 text_entities.push(next_page_entity);
447
448 let right_1 = spawn_small_text_node(commands, "> Btn 2-1".into(), 1000, 120, Justify::Left);
450 let right_2 = spawn_small_text_node(commands, "> Btn 3-1".into(), 1000, 255, Justify::Left);
451 let right_3 = spawn_small_text_node(commands, "> Btn 4-1".into(), 1000, 390, Justify::Left);
452 let left_1 = spawn_small_text_node(commands, "Btn 1-3 < ".into(), 310, 255, Justify::Right);
453 let left_2 = spawn_small_text_node(commands, "Btn 2-3 < ".into(), 310, 390, Justify::Right);
454 let left_3 = spawn_small_text_node(commands, "Btn 3-3 < ".into(), 310, 525, Justify::Right);
455 text_entities.push(right_1);
456 text_entities.push(right_2);
457 text_entities.push(right_3);
458 text_entities.push(left_1);
459 text_entities.push(left_2);
460 text_entities.push(left_3);
461
462 let text = match page_num {
463 0 => Text::new(
465 "Vertical movements disabled on each button, but you can still navigate between rows by going off the left or right sides."
466 ),
467 2 => Text::new(
469 "Vertical Navigation has been manually overridden to be inverted! \
470 ^ moves down, and v (down) moves up.",
471 ),
472 _ => Text::default()
473 };
474 let footer_info = commands
475 .spawn((
476 text,
477 Node {
478 position_type: PositionType::Absolute,
479 left: px(450),
480 top: px(600),
481 width: px(540),
482 padding: UiRect::all(px(12)),
483 ..default()
484 },
485 TextFont {
486 font_size: FontSize::Px(20.0),
487 ..default()
488 },
489 ))
490 .id();
491 text_entities.push(footer_info);
492}
493
494fn setup_buttons_for_triangle_page(
497 commands: &mut Commands,
498 page_num: usize,
499 entities: (&mut Vec<Entity>, &mut Vec<Entity>),
500) {
501 let button_positions = [
502 (450.0, 80.0), (700.0, 80.0), (575.0, 215.0), (1050.0, 350.0), ];
507 let (page_button_entities, text_entities) = entities;
508 for (i, (left, top)) in button_positions.iter().enumerate() {
509 let button_entity =
510 spawn_auto_nav_button(commands, format!("Btn {}", i + 1), left, top, page_num);
511 page_button_entities.push(button_entity);
512 }
513
514 let current_page_entity = spawn_small_text_node(
516 commands,
517 format!("Currently on Page {}", page_num + 1),
518 650,
519 20,
520 Justify::Center,
521 );
522 text_entities.push(current_page_entity);
523
524 let previous_page = if page_num == 0 { 3 } else { page_num };
526 let previous_page_entity = spawn_small_text_node(
527 commands,
528 format!("Page {} << ", previous_page),
529 310,
530 120,
531 Justify::Right,
532 );
533 text_entities.push(previous_page_entity);
534
535 let below_button_three_entity =
537 spawn_small_text_node(commands, "v\nBtn 4".into(), 575, 325, Justify::Center);
538 text_entities.push(below_button_three_entity);
539
540 let right_of_button_three_entity =
542 spawn_small_text_node(commands, "> Btn 4".into(), 735, 255, Justify::Left);
543 text_entities.push(right_of_button_three_entity);
544
545 let below_button_three_entity =
547 spawn_small_text_node(commands, "Btn 3\n^".into(), 1050, 300, Justify::Center);
548 text_entities.push(below_button_three_entity);
549
550 let right_of_button_three_entity =
552 spawn_small_text_node(commands, "Btn 3 < ".into(), 910, 390, Justify::Right);
553 text_entities.push(right_of_button_three_entity);
554
555 let next_page_entity = spawn_small_text_node(
557 commands,
558 format!("V\nV\nPage {}", (page_num + 1) % 3 + 1),
559 1050,
560 460,
561 Justify::Center,
562 );
563 text_entities.push(next_page_entity);
564}
565
566fn spawn_auto_nav_button(
567 commands: &mut Commands,
568 text: String,
569 left: &f64,
570 top: &f64,
571 page_num: usize,
572) -> Entity {
573 commands
574 .spawn((
575 Button,
576 Node {
577 position_type: PositionType::Absolute,
578 left: px(*left),
579 top: px(*top),
580 width: px(140),
581 height: px(100),
582 border: UiRect::all(px(4)),
583 justify_content: JustifyContent::Center,
584 align_items: AlignItems::Center,
585 border_radius: BorderRadius::all(px(12)),
586 ..default()
587 },
588 Page(page_num),
589 BackgroundColor(NORMAL_BUTTON_COLORS[page_num].into()),
590 AutoDirectionalNavigation::default(),
592 ResetTimer::default(),
593 Name::new(text.clone()),
594 ))
595 .with_child((
596 Text::new(text),
597 TextLayout {
598 justify: Justify::Center,
599 ..default()
600 },
601 ))
602 .id()
603}
604
605fn spawn_small_text_node(
606 commands: &mut Commands,
607 text: String,
608 left: i32,
609 top: i32,
610 justify: Justify,
611) -> Entity {
612 commands
613 .spawn((
614 Text::new(text),
615 Node {
616 position_type: PositionType::Absolute,
617 left: px(left),
618 top: px(top),
619 width: px(140),
620 padding: UiRect::all(px(12)),
621 ..default()
622 },
623 TextFont {
624 font_size: FontSize::Px(20.0),
625 ..default()
626 },
627 TextLayout {
628 justify,
629 ..default()
630 },
631 ))
632 .id()
633}
634
635#[derive(Debug, PartialEq, Eq, Hash)]
637enum DirectionalNavigationAction {
638 Up,
639 Down,
640 Left,
641 Right,
642 Select,
643}
644
645impl DirectionalNavigationAction {
646 fn variants() -> Vec<Self> {
647 vec![
648 DirectionalNavigationAction::Up,
649 DirectionalNavigationAction::Down,
650 DirectionalNavigationAction::Left,
651 DirectionalNavigationAction::Right,
652 DirectionalNavigationAction::Select,
653 ]
654 }
655
656 fn keycode(&self) -> KeyCode {
657 match self {
658 DirectionalNavigationAction::Up => KeyCode::ArrowUp,
659 DirectionalNavigationAction::Down => KeyCode::ArrowDown,
660 DirectionalNavigationAction::Left => KeyCode::ArrowLeft,
661 DirectionalNavigationAction::Right => KeyCode::ArrowRight,
662 DirectionalNavigationAction::Select => KeyCode::Enter,
663 }
664 }
665
666 fn gamepad_button(&self) -> GamepadButton {
667 match self {
668 DirectionalNavigationAction::Up => GamepadButton::DPadUp,
669 DirectionalNavigationAction::Down => GamepadButton::DPadDown,
670 DirectionalNavigationAction::Left => GamepadButton::DPadLeft,
671 DirectionalNavigationAction::Right => GamepadButton::DPadRight,
672 DirectionalNavigationAction::Select => GamepadButton::South,
673 }
674 }
675}
676
677#[derive(Default, Resource)]
678struct ActionState {
679 pressed_actions: HashSet<DirectionalNavigationAction>,
680}
681
682fn process_inputs(
683 mut action_state: ResMut<ActionState>,
684 keyboard_input: Res<ButtonInput<KeyCode>>,
685 gamepad_input: Query<&Gamepad>,
686) {
687 action_state.pressed_actions.clear();
688
689 for action in DirectionalNavigationAction::variants() {
690 if keyboard_input.just_pressed(action.keycode()) {
691 action_state.pressed_actions.insert(action);
692 }
693 }
694
695 for gamepad in gamepad_input.iter() {
696 for action in DirectionalNavigationAction::variants() {
697 if gamepad.just_pressed(action.gamepad_button()) {
698 action_state.pressed_actions.insert(action);
699 }
700 }
701 }
702}
703
704fn navigate(
705 action_state: Res<ActionState>,
706 parent_query: Query<&ChildOf>,
707 mut visibility_query: Query<&mut Visibility>,
708 mut auto_directional_navigator: AutoDirectionalNavigator,
709) {
710 let net_east_west = action_state
711 .pressed_actions
712 .contains(&DirectionalNavigationAction::Right) as i8
713 - action_state
714 .pressed_actions
715 .contains(&DirectionalNavigationAction::Left) as i8;
716
717 let net_north_south = action_state
718 .pressed_actions
719 .contains(&DirectionalNavigationAction::Up) as i8
720 - action_state
721 .pressed_actions
722 .contains(&DirectionalNavigationAction::Down) as i8;
723
724 let maybe_direction = Dir2::from_xy(net_east_west as f32, net_north_south as f32)
726 .ok()
727 .map(CompassOctant::from);
728
729 let previous_focus = auto_directional_navigator.input_focus();
731 if let Some(direction) = maybe_direction {
732 match auto_directional_navigator.navigate(direction) {
733 Ok(new_focus) => {
734 if let Ok(current_child_of) = parent_query.get(new_focus)
738 && let Ok(mut current_page_visibility) =
739 visibility_query.get_mut(current_child_of.parent())
740 {
741 *current_page_visibility = Visibility::Visible;
742
743 if let Some(previous_focus_entity) = previous_focus
744 && let Ok(previous_child_of) = parent_query.get(previous_focus_entity)
745 && previous_child_of.parent() != current_child_of.parent()
746 && let Ok(mut previous_page_visibility) =
747 visibility_query.get_mut(previous_child_of.parent())
748 {
749 *previous_page_visibility = Visibility::Hidden;
750 }
751 }
752 }
753 Err(_e) => {
754 }
756 }
757 }
758}
759
760fn update_focus_display(
761 input_focus: Res<InputFocus>,
762 button_query: Query<&Name, With<Button>>,
763 mut display_query: Query<&mut Text, With<FocusDisplay>>,
764) {
765 if let Ok(mut text) = display_query.single_mut() {
766 if let Some(focused_entity) = input_focus.get() {
767 if let Ok(name) = button_query.get(focused_entity) {
768 **text = format!("Focused: {}", name);
769 } else {
770 **text = "Focused: Unknown".to_string();
771 }
772 } else {
773 **text = "Focused: None".to_string();
774 }
775 }
776}
777
778fn update_key_display(
779 keyboard_input: Res<ButtonInput<KeyCode>>,
780 gamepad_input: Query<&Gamepad>,
781 mut display_query: Query<&mut Text, With<KeyDisplay>>,
782) {
783 if let Ok(mut text) = display_query.single_mut() {
784 for action in DirectionalNavigationAction::variants() {
786 if keyboard_input.just_pressed(action.keycode()) {
787 let key_name = match action {
788 DirectionalNavigationAction::Up => "Up Arrow",
789 DirectionalNavigationAction::Down => "Down Arrow",
790 DirectionalNavigationAction::Left => "Left Arrow",
791 DirectionalNavigationAction::Right => "Right Arrow",
792 DirectionalNavigationAction::Select => "Enter",
793 };
794 **text = format!("Last Key: {}", key_name);
795 return;
796 }
797 }
798
799 for gamepad in gamepad_input.iter() {
801 for action in DirectionalNavigationAction::variants() {
802 if gamepad.just_pressed(action.gamepad_button()) {
803 let button_name = match action {
804 DirectionalNavigationAction::Up => "D-Pad Up",
805 DirectionalNavigationAction::Down => "D-Pad Down",
806 DirectionalNavigationAction::Left => "D-Pad Left",
807 DirectionalNavigationAction::Right => "D-Pad Right",
808 DirectionalNavigationAction::Select => "A Button",
809 };
810 **text = format!("Last Key: {}", button_name);
811 return;
812 }
813 }
814 }
815 }
816}
817
818fn highlight_focused_element(
819 input_focus: Res<InputFocus>,
820 input_focus_visible: Res<InputFocusVisible>,
821 mut query: Query<(Entity, &mut BorderColor, &Page)>,
822) {
823 for (entity, mut border_color, page) in query.iter_mut() {
824 if input_focus.get() == Some(entity) && input_focus_visible.0 {
825 *border_color = BorderColor::all(FOCUSED_BORDER_COLORS[page.0]);
826 } else {
827 *border_color = BorderColor::DEFAULT;
828 }
829 }
830}
831
832fn interact_with_focused_button(
833 action_state: Res<ActionState>,
834 input_focus: Res<InputFocus>,
835 mut commands: Commands,
836) {
837 if action_state
838 .pressed_actions
839 .contains(&DirectionalNavigationAction::Select)
840 && let Some(focused_entity) = input_focus.get()
841 {
842 commands.trigger(Pointer::new(
843 PointerId::Mouse,
844 Location {
845 target: NormalizedRenderTarget::None {
846 width: 0,
847 height: 0,
848 },
849 position: Vec2::ZERO,
850 },
851 Click {
852 button: PointerButton::Primary,
853 hit: HitData {
854 camera: Entity::PLACEHOLDER,
855 depth: 0.0,
856 position: None,
857 normal: None,
858 extra: None,
859 },
860 count: 1,
861 duration: Duration::from_secs_f32(0.1),
862 },
863 focused_entity,
864 ));
865 }
866}