ratatui_reactive/core/
focus_manager.rs1use crate::{Component, Render};
2use ratatui::buffer::Buffer;
3use ratatui::layout::Rect;
4use std::marker::PhantomData;
5use sycamore_reactive::{
6 ReadSignal, Signal, create_child_scope, create_signal, on_cleanup, provide_context,
7};
8
9#[derive(Debug)]
10pub struct FocusManager<R> {
11 focus: Signal<usize>,
12 marker: PhantomData<*mut R>,
13}
14
15impl<R> Clone for FocusManager<R> {
16 #[inline]
17 fn clone(&self) -> Self {
18 *self
19 }
20}
21
22impl<R> Copy for FocusManager<R> {}
23
24#[derive(Debug, Clone, Copy)]
25pub struct Focusable {
26 route: usize,
27 focus: ReadSignal<usize>,
28}
29
30#[inline]
31#[cfg_attr(debug_assertions, track_caller)]
32pub fn provide_focus_manager<R: 'static + Into<usize>>(initial: R) -> FocusManager<R> {
33 let focus = create_signal(initial.into());
34 let focus_manager = FocusManager {
35 focus,
36 marker: PhantomData,
37 };
38 provide_context(focus_manager);
39 focus_manager
40}
41
42impl<F: Into<usize>> FocusManager<F> {
43 #[inline]
44 #[cfg_attr(debug_assertions, track_caller)]
45 pub fn on<R: Render + 'static, C: Component<R>>(self, route: F, component: C) -> impl Render {
46 let scope = create_child_scope(|| {
47 provide_context(Focusable {
48 route: route.into(),
49 focus: *self.focus,
50 })
51 });
52 on_cleanup(move || scope.dispose());
53 let wrapped = scope.run_in(move || component.create());
54 move |area: Rect, buf: &mut Buffer| {
55 self.focus.track();
56 wrapped.render(area, buf);
57 }
58 }
59
60 #[inline]
61 #[cfg_attr(debug_assertions, track_caller)]
62 pub fn focus(&self, route: F) {
63 self.focus.set(route.into());
64 }
65}
66
67impl Focusable {
68 #[inline]
69 #[cfg_attr(debug_assertions, track_caller)]
70 pub fn is_focused(&self) -> bool {
71 self.focus.with_untracked(|r| r == &self.route)
72 }
73}