fc_dioxus/behavior/
roving.rs1use crate::{core::state::Orientation, theme::Direction};
8use dioxus::prelude::*;
9use std::rc::Rc;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Move {
14 Next,
16 Previous,
18 First,
20 Last,
22}
23
24#[must_use]
27pub fn intent(key: &Key, orientation: Orientation, direction: Direction) -> Option<Move> {
28 let rtl = direction == Direction::Rtl;
29 match (key, orientation) {
30 (Key::Home, _) => Some(Move::First),
31 (Key::End, _) => Some(Move::Last),
32 (Key::ArrowDown, Orientation::Vertical) => Some(Move::Next),
33 (Key::ArrowUp, Orientation::Vertical) => Some(Move::Previous),
34 (Key::ArrowRight, Orientation::Horizontal) => {
35 Some(if rtl { Move::Previous } else { Move::Next })
36 }
37 (Key::ArrowLeft, Orientation::Horizontal) => {
38 Some(if rtl { Move::Next } else { Move::Previous })
39 }
40 _ => None,
41 }
42}
43
44#[must_use]
59pub fn target(
60 len: usize,
61 current: usize,
62 step: Move,
63 reachable: impl Fn(usize) -> bool,
64) -> Option<usize> {
65 if len == 0 {
66 return None;
67 }
68 match step {
69 Move::First => (0..len).find(|&i| reachable(i)),
70 Move::Last => (0..len).rev().find(|&i| reachable(i)),
71 Move::Next => (1..=len)
72 .map(|n| (current + n) % len)
73 .find(|&i| reachable(i)),
74 Move::Previous => (1..=len)
75 .map(|n| (current + len - n % len) % len)
76 .find(|&i| reachable(i)),
77 }
78}
79
80#[derive(Clone)]
82pub(crate) struct Member {
83 pub(crate) key: u64,
84 pub(crate) value: String,
85 pub(crate) focusable: bool,
87 pub(crate) enabled: bool,
89 pub(crate) mounted: Option<Rc<MountedData>>,
90}
91
92#[derive(Clone, Copy)]
95pub(crate) struct Roster {
96 members: Signal<Vec<Member>>,
97 next: Signal<u64>,
98}
99impl Roster {
100 pub(crate) fn new() -> Self {
101 Self {
102 members: Signal::new(Vec::new()),
103 next: Signal::new(0),
104 }
105 }
106 pub(crate) fn peek(&self) -> Vec<Member> {
108 self.members.peek().clone()
109 }
110 pub(crate) fn read(&self) -> Vec<Member> {
112 self.members.read().clone()
113 }
114 fn join(mut self, value: String, focusable: bool, enabled: bool) -> u64 {
115 let key = *self.next.peek();
116 self.next.set(key + 1);
117 self.members.write().push(Member {
118 key,
119 value,
120 focusable,
121 enabled,
122 mounted: None,
123 });
124 key
125 }
126 fn update(mut self, key: u64, value: &str, focusable: bool, enabled: bool) {
127 let stale = self.members.peek().iter().any(|m| {
128 m.key == key && (m.value != value || m.focusable != focusable || m.enabled != enabled)
129 });
130 if stale {
131 if let Some(member) = self.members.write().iter_mut().find(|m| m.key == key) {
132 value.clone_into(&mut member.value);
133 member.focusable = focusable;
134 member.enabled = enabled;
135 }
136 }
137 }
138 pub(crate) fn mount(mut self, key: u64, mounted: Rc<MountedData>) {
139 if let Some(member) = self.members.write().iter_mut().find(|m| m.key == key) {
140 member.mounted = Some(mounted);
141 }
142 }
143 fn leave(mut self, key: u64) {
144 self.members.write().retain(|m| m.key != key);
145 }
146}
147
148pub(crate) fn use_member(value: &str, focusable: bool, enabled: bool) -> Option<(Roster, u64)> {
151 let roster = try_consume_context::<Roster>();
152 let joined = use_hook(|| roster.map(|r| (r, r.join(value.to_owned(), focusable, enabled))));
153 if let Some((roster, key)) = joined {
154 roster.update(key, value, focusable, enabled);
155 }
156 use_drop(move || {
157 if let Some((roster, key)) = joined {
158 roster.leave(key);
159 }
160 });
161 joined
162}
163
164pub(crate) fn focus(member: &Member) {
166 if let Some(mounted) = member.mounted.clone() {
167 spawn(async move {
168 let _ = mounted.set_focus(true).await;
169 });
170 }
171}