mecomp_tui/state/
component.rs1use tokio::sync::{
2 broadcast,
3 mpsc::{UnboundedReceiver, UnboundedSender},
4};
5
6use crate::termination::Interrupted;
7
8use super::action::ComponentAction;
9
10#[derive(Debug, Clone)]
12#[allow(clippy::module_name_repetitions)]
13pub struct ComponentState {
14 state_tx: UnboundedSender<ActiveComponent>,
15}
16
17impl ComponentState {
18 #[must_use]
20 pub fn new() -> (Self, UnboundedReceiver<ActiveComponent>) {
21 let (state_tx, state_rx) = tokio::sync::mpsc::unbounded_channel::<ActiveComponent>();
22
23 (Self { state_tx }, state_rx)
24 }
25
26 pub async fn main_loop(
32 &self,
33 mut action_rx: UnboundedReceiver<ComponentAction>,
34 mut interrupt_rx: broadcast::Receiver<Interrupted>,
35 ) -> anyhow::Result<Interrupted> {
36 let mut state = ActiveComponent::default();
37
38 self.state_tx.send(state)?;
40
41 let result = loop {
42 tokio::select! {
43 Some(action) = action_rx.recv() => {
45 state = Self::handle_action(state, action);
46 self.state_tx.send(state)?;
47 },
48 Ok(interrupted) = interrupt_rx.recv() => {
50 break interrupted;
51 }
52 }
53 };
54
55 Ok(result)
56 }
57
58 #[must_use]
60 const fn handle_action(state: ActiveComponent, action: ComponentAction) -> ActiveComponent {
61 match action {
62 ComponentAction::Next => state.next(),
63 ComponentAction::Previous => state.prev(),
64 ComponentAction::Set(new_state) => new_state,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
70#[allow(clippy::module_name_repetitions)]
71pub enum ActiveComponent {
72 #[default]
73 Sidebar,
74 QueueBar,
75 ControlPanel,
76 ContentView,
77}
78
79impl ActiveComponent {
80 #[must_use]
81 pub const fn next(self) -> Self {
82 match self {
83 Self::Sidebar => Self::ContentView,
84 Self::ContentView => Self::QueueBar,
85 Self::QueueBar => Self::ControlPanel,
86 Self::ControlPanel => Self::Sidebar,
87 }
88 }
89
90 #[must_use]
91 pub const fn prev(self) -> Self {
92 match self {
93 Self::Sidebar => Self::ControlPanel,
94 Self::ContentView => Self::Sidebar,
95 Self::QueueBar => Self::ContentView,
96 Self::ControlPanel => Self::QueueBar,
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use rstest::rstest;
104
105 use super::*;
106
107 #[test]
108 fn test_next() {
109 assert_eq!(
110 ActiveComponent::Sidebar.next(),
111 ActiveComponent::ContentView
112 );
113 assert_eq!(
114 ActiveComponent::ContentView.next(),
115 ActiveComponent::QueueBar
116 );
117 assert_eq!(
118 ActiveComponent::QueueBar.next(),
119 ActiveComponent::ControlPanel
120 );
121 assert_eq!(
122 ActiveComponent::ControlPanel.next(),
123 ActiveComponent::Sidebar
124 );
125 }
126
127 #[test]
128 fn test_prev() {
129 assert_eq!(
130 ActiveComponent::Sidebar.prev(),
131 ActiveComponent::ControlPanel
132 );
133 assert_eq!(
134 ActiveComponent::ContentView.prev(),
135 ActiveComponent::Sidebar
136 );
137 assert_eq!(
138 ActiveComponent::QueueBar.prev(),
139 ActiveComponent::ContentView
140 );
141 assert_eq!(
142 ActiveComponent::ControlPanel.prev(),
143 ActiveComponent::QueueBar
144 );
145 }
146
147 #[rstest]
148 #[case::next(
149 ActiveComponent::Sidebar,
150 ComponentAction::Next,
151 ActiveComponent::ContentView
152 )]
153 #[case::next(
154 ActiveComponent::ContentView,
155 ComponentAction::Next,
156 ActiveComponent::QueueBar
157 )]
158 #[case::next(
159 ActiveComponent::QueueBar,
160 ComponentAction::Next,
161 ActiveComponent::ControlPanel
162 )]
163 #[case::next(
164 ActiveComponent::ControlPanel,
165 ComponentAction::Next,
166 ActiveComponent::Sidebar
167 )]
168 #[case::prev(
169 ActiveComponent::Sidebar,
170 ComponentAction::Previous,
171 ActiveComponent::ControlPanel
172 )]
173 #[case::prev(
174 ActiveComponent::ContentView,
175 ComponentAction::Previous,
176 ActiveComponent::Sidebar
177 )]
178 #[case::prev(
179 ActiveComponent::QueueBar,
180 ComponentAction::Previous,
181 ActiveComponent::ContentView
182 )]
183 #[case::prev(
184 ActiveComponent::ControlPanel,
185 ComponentAction::Previous,
186 ActiveComponent::QueueBar
187 )]
188 #[case::set(
189 ActiveComponent::Sidebar,
190 ComponentAction::Set(ActiveComponent::ContentView),
191 ActiveComponent::ContentView
192 )]
193 #[case::set(
194 ActiveComponent::ContentView,
195 ComponentAction::Set(ActiveComponent::QueueBar),
196 ActiveComponent::QueueBar
197 )]
198 fn test_handle_action(
199 #[case] starting_state: ActiveComponent,
200 #[case] action: ComponentAction,
201 #[case] expected_state: ActiveComponent,
202 ) {
203 let new_state = ComponentState::handle_action(starting_state, action);
204
205 assert_eq!(new_state, expected_state);
206 }
207}