1use std::{cell::RefCell, fmt::Debug, rc::Rc};
14
15use cranpose_core::{NodeId, collections::map::HashSet};
16use cranpose_foundation::{PointerButtons, PointerSource, RotaryScrollEvent};
17use cranpose_render_common::Renderer;
18use cranpose_ui::{
19 LayoutTree, PlatformTextInputHandler, SemanticsTree, pointer_icon_session::PointerIconState,
20};
21use cranpose_ui_graphics::{Point, PointerIcon, Size};
22use web_time::Instant;
23
24use crate::{
25 AppShell, DevOverlayControl, FramePacingMode, FrameRatePreference, FrameSchedule,
26 FrameScheduler, FrameUpdateResult, PlatformFrameDriver, ShellApp, SurfaceRoutingScratch,
27 hit_path_tracker::{HitPathTracker, PointerId},
28};
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum RootId {
35 Primary,
37 Window(u64),
39}
40
41pub struct RootSurface<R: Renderer> {
49 pub(crate) id: RootId,
50 pub(crate) root: Option<NodeId>,
51 pub(crate) renderer: R,
52 pub(crate) cursor: (f32, f32),
53 pub(crate) viewport: (f32, f32),
54 pub(crate) buffer_size: (u32, u32),
55 pub(crate) layout_tree: Option<LayoutTree>,
56 pub(crate) semantics_tree: Option<SemanticsTree>,
57 semantics_moved: bool,
60 pub(crate) modal_focus: Vec<(NodeId, Option<NodeId>)>,
61 pub(crate) frame_rate_preference: FrameRatePreference,
62 pub(crate) scene_dirty: bool,
63 pub(crate) scoped_layout_scene_nodes: Vec<NodeId>,
64 pub(crate) scoped_moved_scene_nodes: Vec<NodeId>,
66 pub(crate) scoped_draw_nodes: Vec<NodeId>,
67 pub(crate) scoped_layer_property_nodes: Vec<NodeId>,
68 pub(crate) structural_scene_nodes: Vec<NodeId>,
69 pub(crate) partial_scene_nodes: Vec<NodeId>,
70 pub(crate) retained_visual_nodes: HashSet<NodeId>,
71 pub(crate) is_dirty: bool,
72 pub(crate) buttons_pressed: PointerButtons,
73 pub(crate) pointer_source: PointerSource,
74 pub(crate) hit_path_tracker: HitPathTracker,
75 pub(crate) hovered_nodes: Vec<NodeId>,
76 pub(crate) on_rotary_scroll: Option<Rc<dyn Fn(RotaryScrollEvent) -> bool>>,
77 pub(crate) dev_overlay_controls: Vec<DevOverlayControl>,
78 pub(crate) inspector: crate::inspector::DeveloperInspector,
79 pub(crate) dev_overlay_text: String,
80 pub(crate) dev_overlay_last_refresh: Option<Instant>,
81 pub(crate) dev_overlay_viewport: Option<Size>,
82 pub(crate) frame_scheduler: FrameScheduler,
83 pub(crate) pointer_icon: PointerIconState,
84 pub(crate) last_update: FrameUpdateResult,
85 pub(crate) frame_owed: bool,
86 pub(crate) screen_origin: Option<Point>,
87}
88
89impl<R: Renderer> RootSurface<R> {
90 pub(crate) fn new(
91 id: RootId,
92 renderer: R,
93 buffer_size: (u32, u32),
94 viewport: (f32, f32),
95 ) -> Self {
96 Self {
97 id,
98 root: None,
99 renderer,
100 cursor: (0.0, 0.0),
101 viewport,
102 buffer_size,
103 layout_tree: None,
104 semantics_tree: None,
105 semantics_moved: false,
106 modal_focus: Vec::new(),
107 frame_rate_preference: FrameRatePreference::default(),
108 scene_dirty: true,
109 scoped_layout_scene_nodes: Vec::new(),
110 scoped_moved_scene_nodes: Vec::new(),
111 scoped_draw_nodes: Vec::new(),
112 scoped_layer_property_nodes: Vec::new(),
113 structural_scene_nodes: Vec::new(),
114 partial_scene_nodes: Vec::new(),
115 retained_visual_nodes: HashSet::default(),
116 is_dirty: true,
117 buttons_pressed: PointerButtons::NONE,
118 pointer_source: PointerSource::Unknown,
119 hit_path_tracker: HitPathTracker::new(),
120 hovered_nodes: Vec::new(),
121 on_rotary_scroll: None,
122 dev_overlay_controls: Vec::new(),
123 inspector: crate::inspector::DeveloperInspector::default(),
124 dev_overlay_text: String::new(),
125 dev_overlay_last_refresh: None,
126 dev_overlay_viewport: None,
127 frame_scheduler: FrameScheduler::default(),
128 pointer_icon: PointerIconState::new(),
129 last_update: FrameUpdateResult::default(),
130 frame_owed: false,
131 screen_origin: None,
132 }
133 }
134
135 pub(crate) fn root_node(&self, app: &ShellApp) -> Option<NodeId> {
136 match self.id {
137 RootId::Primary => app.composition.root(),
138 RootId::Window(_) => self.root,
139 }
140 }
141
142 pub(crate) fn owns_nodes_under(&self, window_root: Option<NodeId>) -> bool {
143 match self.id {
144 RootId::Primary => window_root.is_none(),
145 RootId::Window(_) => self.root.is_some() && self.root == window_root,
146 }
147 }
148
149 pub(crate) fn viewport_size(&self) -> Size {
150 Size {
151 width: self.viewport.0,
152 height: self.viewport.1,
153 }
154 }
155
156 pub(crate) fn screen_point_inside(&self, screen: Point) -> Option<Point> {
157 let origin = self.screen_origin?;
158 let local = Point {
159 x: screen.x - origin.x,
160 y: screen.y - origin.y,
161 };
162 (local.x >= 0.0
163 && local.y >= 0.0
164 && local.x <= self.viewport.0
165 && local.y <= self.viewport.1)
166 .then_some(local)
167 }
168
169 pub(crate) fn set_root(&mut self, root: Option<NodeId>) {
170 if self.root == root {
171 return;
172 }
173 self.root = root;
174 self.forget_snapshots();
175 self.invalidate_scene_root(root);
176 self.retained_visual_nodes.clear();
177 self.hit_path_tracker.clear();
178 self.hovered_nodes.clear();
179 self.buttons_pressed = PointerButtons::NONE;
180 self.is_dirty = true;
181 }
182
183 pub(crate) fn invalidate_scene_root(&mut self, root: Option<NodeId>) {
184 self.scoped_layout_scene_nodes.clear();
185 self.scoped_layout_scene_nodes.extend(root);
186 self.scoped_moved_scene_nodes.clear();
187 self.scoped_draw_nodes.clear();
188 self.scoped_layer_property_nodes.clear();
189 self.structural_scene_nodes.clear();
190 self.partial_scene_nodes.clear();
191 self.scene_dirty = true;
192 }
193
194 pub(crate) fn forget_snapshots(&mut self) {
197 self.layout_tree = None;
198 self.semantics_moved = true;
199 }
200
201 pub(crate) fn has_active_pointer_gesture(&self) -> bool {
202 self.buttons_pressed != PointerButtons::NONE
203 && self.hit_path_tracker.has_path(PointerId::PRIMARY)
204 }
205
206 pub(crate) fn renderer_warmup_due(&self, app: &ShellApp) -> bool {
207 self.renderer.needs_frame_warmup() && !app.runtime.runtime_handle().has_frame_callbacks()
208 }
209
210 pub(crate) fn needs_redraw_in_context(&self, app: &ShellApp) -> bool {
211 app.has_stale_work_in_context()
212 || app.composition.should_recompose()
213 || self.is_dirty
214 || self.scene_dirty
215 || self.renderer_warmup_due(app)
216 }
217
218 pub(crate) fn compute_frame_schedule(
219 &self,
220 app: &ShellApp,
221 surfaces_dirty: bool,
222 ) -> FrameSchedule {
223 let app_context = Rc::clone(&app.app_context);
224 let (needs_update, needs_frame) = app_context.enter(|| {
225 let needs_frame = self.is_dirty
226 || self.scene_dirty
227 || app.wants_frame_in_context()
228 || self.has_active_pointer_gesture()
229 || self.renderer_warmup_due(app);
230 (app.needs_ui_update_in_context(surfaces_dirty), needs_frame)
231 });
232 FrameSchedule {
233 needs_update,
234 needs_frame,
235 next_deadline: app.next_event_time(),
236 }
237 }
238
239 pub(crate) fn invalidate_dev_overlay_text(&mut self) {
240 self.dev_overlay_text.clear();
241 self.dev_overlay_last_refresh = None;
242 self.dev_overlay_viewport = None;
243 }
244
245 pub(crate) fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
246 self.dev_overlay_controls
247 .iter()
248 .find(|control| control.mode == mode)
249 .map(|control| {
250 (
251 control.bounds.x + control.bounds.width * 0.5,
252 control.bounds.y + control.bounds.height * 0.5,
253 )
254 })
255 }
256
257 pub(crate) fn layout_tree_in_context(&mut self, app: &mut ShellApp) -> Option<&LayoutTree> {
258 if self.layout_tree.is_none() {
259 let root = self.root_node(app)?;
260 let mut applier = app.composition.applier_mut();
261 match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
262 Ok(layout_tree) => {
263 self.layout_tree = layout_tree;
264 }
265 Err(err) => {
266 log::debug!("failed to build layout snapshot: {err}");
267 return None;
268 }
269 }
270 }
271 self.layout_tree.as_ref()
272 }
273
274 pub(crate) fn top_modal(&self, app: &mut ShellApp) -> Option<NodeId> {
277 let root = self.root_node(app)?;
278 let mut applier = app.composition.applier_mut();
279 cranpose_ui::top_modal_from_applier(&mut applier, root).unwrap_or_else(|err| {
280 log::debug!("failed to find the top modal under root #{root}: {err}");
281 None
282 })
283 }
284
285 pub(crate) fn semantics_tree_in_context(
286 &mut self,
287 app: &mut ShellApp,
288 ) -> Option<&SemanticsTree> {
289 if !app.semantics_enabled && !self.inspector.state.open {
290 return None;
291 }
292 self.semantics_tree_for_input(app)
293 }
294
295 pub(crate) fn semantics_tree_for_input(
296 &mut self,
297 app: &mut ShellApp,
298 ) -> Option<&SemanticsTree> {
299 app.flush_semantics_invalidations();
300 let root = self.root_node(app)?;
301 let semantics_dirty = {
302 let mut applier = app.composition.applier_mut();
303 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
304 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
305 true
306 })
307 };
308 if self.semantics_tree.is_none() || self.semantics_moved || semantics_dirty {
309 let mut applier = app.composition.applier_mut();
310 match cranpose_ui::update_semantics_tree_from_applier(
311 &mut applier,
312 root,
313 &mut self.semantics_tree,
314 ) {
315 Ok(()) => {
316 #[cfg(debug_assertions)]
317 crate::semantics_check::assert_matches_rebuild(
318 &mut applier,
319 root,
320 self.semantics_tree.as_ref(),
321 );
322 self.semantics_moved = false;
323 app.semantics_snapshot_revision =
324 app.semantics_snapshot_revision.wrapping_add(1);
325 }
326 Err(err) => {
327 log::debug!("failed to build semantics snapshot: {err}");
328 return None;
329 }
330 }
331 }
332 self.semantics_tree.as_ref()
333 }
334}
335
336#[derive(Default)]
337pub(crate) struct TextInputRoutes {
338 active: Option<RootId>,
339 shown: Option<RootId>,
340 handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
341}
342
343impl TextInputRoutes {
344 pub(crate) fn active(&self) -> RootId {
345 self.active.unwrap_or(RootId::Primary)
346 }
347
348 pub(crate) fn set_active(&mut self, root: RootId) {
349 self.active = Some(root);
350 }
351
352 pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
353 self.remove(root);
354 self.handlers.push((root, handler));
355 }
356
357 pub(crate) fn remove(&mut self, root: RootId) {
358 self.handlers.retain(|(id, _)| *id != root);
359 if self.shown == Some(root) {
360 self.shown = None;
361 }
362 }
363
364 fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
365 self.handlers
366 .iter()
367 .find(|(id, _)| *id == root)
368 .map(|(_, handler)| Rc::clone(handler))
369 }
370
371 fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
372 let active = self.active();
373 let handler = self.handler(active);
374 if handler.is_some() {
375 self.shown = Some(active);
376 }
377 handler
378 }
379
380 fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
381 let target = self.shown.take().unwrap_or_else(|| self.active());
382 self.handler(target)
383 }
384}
385
386pub(crate) struct TextInputRouter {
387 pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
388}
389
390impl PlatformTextInputHandler for TextInputRouter {
391 fn show_keyboard(&self) {
392 let handler = self.routes.borrow_mut().take_show_target();
393 if let Some(handler) = handler {
394 handler.show_keyboard();
395 }
396 }
397
398 fn hide_keyboard(&self) {
399 let handler = self.routes.borrow_mut().take_hide_target();
400 if let Some(handler) = handler {
401 handler.hide_keyboard();
402 }
403 }
404}
405
406#[derive(Clone, Copy)]
407pub(crate) enum SurfaceDirtyLane {
408 Layout,
409 Moved,
410 Draw,
411 LayerProperties,
412 Structural,
413}
414
415fn append_surface_dirty_node(
416 surface: &mut RootSurface<impl Renderer>,
417 surface_index: usize,
418 node: NodeId,
419 lane: SurfaceDirtyLane,
420 seen: &mut HashSet<(usize, NodeId)>,
421) {
422 match lane {
423 SurfaceDirtyLane::Layout => {
424 surface.scene_dirty = true;
425 if seen.insert((surface_index, node)) {
426 surface.scoped_layout_scene_nodes.push(node);
427 }
428 }
429 SurfaceDirtyLane::Moved => {
430 surface.scene_dirty = true;
431 if seen.insert((surface_index, node)) {
432 surface.scoped_moved_scene_nodes.push(node);
433 }
434 }
435 SurfaceDirtyLane::Draw => surface.scoped_draw_nodes.push(node),
436 SurfaceDirtyLane::LayerProperties => surface.scoped_layer_property_nodes.push(node),
437 SurfaceDirtyLane::Structural => {
438 if seen.insert((surface_index, node)) {
439 surface.structural_scene_nodes.push(node);
440 }
441 }
442 }
443}
444
445fn queued_scene_nodes<R: Renderer>(surface: &RootSurface<R>, lane: SurfaceDirtyLane) -> &[NodeId] {
448 match lane {
449 SurfaceDirtyLane::Layout => &surface.scoped_layout_scene_nodes,
450 SurfaceDirtyLane::Moved => &surface.scoped_moved_scene_nodes,
451 SurfaceDirtyLane::Draw
452 | SurfaceDirtyLane::LayerProperties
453 | SurfaceDirtyLane::Structural => &[],
454 }
455}
456
457pub(crate) fn route_nodes_by_surface<R: Renderer>(
458 app: &mut ShellApp,
459 surfaces: &mut [RootSurface<R>],
460 nodes: impl IntoIterator<Item = NodeId>,
461 lane: SurfaceDirtyLane,
462 scratch: &mut SurfaceRoutingScratch,
463) {
464 let Some(primary_root) = app.composition.root() else {
465 scratch.attached.clear();
466 scratch.owners.clear();
467 return;
468 };
469 let mut applier = app.composition.applier_mut();
470 applier.scene_nodes_attached_to_into(
471 nodes,
472 primary_root,
473 &mut scratch.attached,
474 &mut scratch.attachment,
475 );
476 if scratch.attached.is_empty() {
477 scratch.owners.clear();
478 return;
479 }
480 scratch.seen.clear();
481 for (index, surface) in surfaces.iter().enumerate() {
482 scratch.seen.extend(
483 queued_scene_nodes(surface, lane)
484 .iter()
485 .map(|node| (index, *node)),
486 );
487 }
488 if app.app_context.window_roots().is_empty() {
489 scratch.owners.clear();
490 if let Some(index) = surfaces
491 .iter()
492 .position(|surface| surface.id == RootId::Primary)
493 {
494 let surface = &mut surfaces[index];
495 for node in scratch.attached.iter().flatten().copied() {
496 append_surface_dirty_node(surface, index, node, lane, &mut scratch.seen);
497 }
498 }
499 return;
500 }
501 cranpose_ui::nearest_window_roots_into(
502 &mut applier,
503 scratch.attached.iter().flatten().copied(),
504 &mut scratch.owners,
505 &mut scratch.window_roots,
506 );
507 for (node, owner) in scratch.attached.iter().flatten().zip(&scratch.owners) {
508 let node = *node;
509 if let Some(index) = surfaces
510 .iter()
511 .position(|surface| surface.owns_nodes_under(*owner))
512 {
513 let surface = &mut surfaces[index];
514 append_surface_dirty_node(surface, index, node, lane, &mut scratch.seen);
515 }
516 }
517}
518
519pub struct SurfaceMut<'a, R: Renderer> {
526 pub(crate) shell: &'a mut AppShell<R>,
527 pub(crate) index: usize,
528}
529
530impl<'a, R> SurfaceMut<'a, R>
531where
532 R: Renderer,
533 R::Error: Debug,
534{
535 pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
536 Self { shell, index }
537 }
538
539 pub fn shell(&mut self) -> &mut AppShell<R> {
542 self.shell
543 }
544
545 pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
546 &mut self.shell.app
547 }
548
549 pub(crate) fn shell_app_ref(&self) -> &ShellApp {
550 &self.shell.app
551 }
552
553 pub(crate) fn surface(&self) -> &RootSurface<R> {
554 &self.shell.surfaces[self.index]
555 }
556
557 pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
558 &mut self.shell.surfaces[self.index]
559 }
560
561 pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
562 let shell = &mut *self.shell;
563 (&mut shell.app, &mut shell.surfaces[self.index])
564 }
565
566 pub fn id(&self) -> RootId {
568 self.surface().id
569 }
570
571 pub fn root(&self) -> Option<NodeId> {
573 let surface = self.surface();
574 surface.root_node(self.shell_app_ref())
575 }
576
577 pub fn renderer(&mut self) -> &mut R {
579 &mut self.surface_mut().renderer
580 }
581
582 pub fn with_renderer<T>(&mut self, block: impl FnOnce(&mut R) -> T) -> T {
585 let (app, surface) = self.parts();
586 app.app_context.enter(|| block(&mut surface.renderer))
587 }
588
589 pub fn scene(&self) -> &R::Scene {
591 self.surface().renderer.scene()
592 }
593
594 pub fn set_viewport(&mut self, width: f32, height: f32) {
602 self.surface_mut().viewport = (width, height);
603 match self.id() {
604 RootId::Primary => self.shell_app().request_forced_layout_pass(),
605 RootId::Window(_) => {
606 if let Some(root) = self.root() {
607 let app_context = Rc::clone(&self.shell_app_ref().app_context);
608 app_context.enter(|| cranpose_ui::schedule_measure_repass(root));
609 }
610 self.shell_app().request_layout_pass();
611 }
612 }
613 let root = self.root();
614 self.surface_mut().invalidate_scene_root(root);
615 self.mark_dirty();
616 }
617
618 pub fn set_screen_origin(&mut self, origin: Option<Point>) {
624 self.surface_mut().screen_origin = origin;
625 }
626
627 pub fn screen_origin(&self) -> Option<Point> {
630 self.surface().screen_origin
631 }
632
633 pub fn viewport_size(&self) -> (f32, f32) {
635 self.surface().viewport
636 }
637
638 pub fn set_buffer_size(&mut self, width: u32, height: u32) {
640 self.surface_mut().buffer_size = (width, height);
641 }
642
643 pub fn buffer_size(&self) -> (u32, u32) {
645 self.surface().buffer_size
646 }
647
648 pub fn mark_dirty(&mut self) {
650 self.surface_mut().is_dirty = true;
651 }
652
653 pub fn needs_redraw(&self) -> bool {
657 let app_context = Rc::clone(&self.shell_app_ref().app_context);
658 app_context.enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
659 }
660
661 pub fn has_active_pointer_gesture(&self) -> bool {
664 self.surface().has_active_pointer_gesture()
665 }
666
667 pub fn last_update_result(&self) -> FrameUpdateResult {
670 self.surface().last_update
671 }
672
673 pub fn frame_owed(&self) -> bool {
678 self.surface().frame_owed
679 }
680
681 pub fn take_frame_owed(&mut self) -> bool {
683 std::mem::take(&mut self.surface_mut().frame_owed)
684 }
685
686 fn compute_frame_schedule(&self) -> FrameSchedule {
687 self.surface()
688 .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
689 }
690
691 pub fn frame_schedule(&self) -> FrameSchedule {
694 let schedule = self.compute_frame_schedule();
695 self.surface().frame_scheduler.record(schedule);
696 schedule
697 }
698
699 pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
701 where
702 D: PlatformFrameDriver + ?Sized,
703 {
704 let schedule = self.compute_frame_schedule();
705 self.surface().frame_scheduler.schedule(schedule, driver);
706 schedule
707 }
708
709 pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
711 self.surface().frame_scheduler.snapshot()
712 }
713
714 pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
717 self.surface_mut().frame_rate_preference = preference;
718 }
719
720 pub fn frame_rate_preference(&self) -> FrameRatePreference {
722 self.surface().frame_rate_preference
723 }
724
725 pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
728 self.surface().dev_overlay_control_center(mode)
729 }
730
731 pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
732 if !self.shell_app_ref().dev_options.frame_pacing_controls {
733 return false;
734 }
735 let Some(mode) = self
736 .surface()
737 .dev_overlay_controls
738 .iter()
739 .find(|control| control.bounds.contains(x, y))
740 .map(|control| control.mode)
741 else {
742 return false;
743 };
744 self.shell().set_frame_pacing_mode(mode);
745 true
746 }
747
748 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
750 let (app, surface) = self.parts();
751 let app_context = Rc::clone(&app.app_context);
752 app_context.enter(|| block(surface.layout_tree_in_context(app)))
753 }
754
755 pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
758 let (app, surface) = self.parts();
759 let app_context = Rc::clone(&app.app_context);
760 app_context.enter(|| block(surface.semantics_tree_in_context(app)))
761 }
762
763 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
766 self.surface().pointer_icon.take_change()
767 }
768
769 pub fn refresh_pointer_icon(&self) {
772 self.surface().pointer_icon.refresh();
773 }
774
775 pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
780 let id = self.id();
781 let app = self.shell_app();
782 app.text_input_routes.borrow_mut().set_handler(id, handler);
783 app.install_text_input_router();
784 }
785
786 pub fn activate(&mut self) {
789 let id = self.id();
790 self.shell_app()
791 .text_input_routes
792 .borrow_mut()
793 .set_active(id);
794 }
795}