1use std::{
2 borrow::BorrowMut,
3 sync::{
4 atomic::{AtomicBool, Ordering},
5 Arc,
6 },
7};
8
9use crossbeam_channel::unbounded;
10use parking_lot::Mutex;
11
12use crate::{RenderSlice, ViewAction, ViewData};
13
14#[derive(Clone, Debug)]
16pub struct State {
17 ended: Arc<AtomicBool>,
18 paused: Arc<AtomicBool>,
19 render_slice: Arc<Mutex<RenderSlice>>,
20 pub(crate) update_receiver: crossbeam_channel::Receiver<ViewAction>,
21 update_sender: crossbeam_channel::Sender<ViewAction>,
22}
23
24impl State {
25 #[inline]
27 #[must_use]
28 pub fn new() -> Self {
29 let (update_sender, update_receiver) = unbounded();
30 Self {
31 ended: Arc::new(AtomicBool::from(false)),
32 paused: Arc::new(AtomicBool::from(false)),
33 render_slice: Arc::new(Mutex::new(RenderSlice::new())),
34 update_receiver,
35 update_sender,
36 }
37 }
38
39 fn send_update(&self, action: ViewAction) {
40 self.update_sender.send(action).unwrap();
41 }
42
43 pub(crate) fn update_receiver(&self) -> crossbeam_channel::Receiver<ViewAction> {
44 self.update_receiver.clone()
45 }
46
47 pub(crate) fn is_ended(&self) -> bool {
48 self.ended.load(Ordering::Relaxed)
49 }
50
51 pub(crate) fn is_paused(&self) -> bool {
52 self.paused.load(Ordering::Relaxed)
53 }
54
55 #[inline]
57 #[must_use]
58 pub fn render_slice(&self) -> Arc<Mutex<RenderSlice>> {
59 Arc::clone(&self.render_slice)
60 }
61
62 #[inline]
67 pub fn start(&self) {
68 self.paused.store(false, Ordering::Release);
69 self.send_update(ViewAction::Start);
70 }
71
72 #[inline]
77 pub fn stop(&self) {
78 self.paused.store(true, Ordering::Release);
79 self.send_update(ViewAction::Stop);
80 }
81
82 #[inline]
87 pub fn end(&self) {
88 self.ended.store(true, Ordering::Release);
89 self.stop();
90 self.send_update(ViewAction::End);
91 }
92
93 #[inline]
98 pub fn refresh(&self) {
99 self.send_update(ViewAction::Refresh);
100 }
101
102 #[inline]
104 pub fn scroll_top(&self) {
105 self.render_slice.lock().borrow_mut().record_scroll_top();
106 }
107
108 #[inline]
110 pub fn scroll_bottom(&self) {
111 self.render_slice.lock().borrow_mut().record_scroll_bottom();
112 }
113
114 #[inline]
116 pub fn scroll_up(&self) {
117 self.render_slice.lock().borrow_mut().record_scroll_up();
118 }
119
120 #[inline]
122 pub fn scroll_down(&self) {
123 self.render_slice.lock().borrow_mut().record_scroll_down();
124 }
125
126 #[inline]
128 pub fn scroll_left(&self) {
129 self.render_slice.lock().borrow_mut().record_scroll_left();
130 }
131
132 #[inline]
134 pub fn scroll_right(&self) {
135 self.render_slice.lock().borrow_mut().record_scroll_right();
136 }
137
138 #[inline]
140 pub fn scroll_page_up(&self) {
141 self.render_slice.lock().borrow_mut().record_page_up();
142 }
143
144 #[inline]
146 pub fn scroll_page_down(&self) {
147 self.render_slice.lock().borrow_mut().record_page_down();
148 }
149
150 #[inline]
152 pub fn resize(&self, width: u16, height: u16) {
153 self.render_slice
154 .lock()
155 .borrow_mut()
156 .record_resize(width as usize, height as usize);
157 }
158
159 #[inline]
164 pub fn render(&self, view_data: &ViewData) {
165 self.render_slice.lock().borrow_mut().sync_view_data(view_data);
166 self.send_update(ViewAction::Render);
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::{
173 testutil::{render_view_line, with_view_state},
174 ViewData,
175 ViewLine,
176 };
177
178 #[test]
179 fn start() {
180 with_view_state(|context| {
181 context.state.start();
182 context.assert_sent_messages(vec!["Start"]);
183 assert!(!context.state.is_paused());
184 });
185 }
186
187 #[test]
188 fn stop() {
189 with_view_state(|context| {
190 context.state.stop();
191 context.assert_sent_messages(vec!["Stop"]);
192 assert!(context.state.is_paused());
193 });
194 }
195
196 #[test]
197 fn end() {
198 with_view_state(|context| {
199 context.state.end();
200 context.assert_sent_messages(vec!["Stop", "End"]);
201 });
202 }
203
204 #[test]
205 fn refresh() {
206 with_view_state(|context| {
207 context.state.refresh();
208 context.assert_sent_messages(vec!["Refresh"]);
209 });
210 }
211
212 #[test]
213 fn scroll_top() {
214 with_view_state(|context| {
215 context.state.scroll_top();
216 context.assert_render_action(&["ScrollTop"]);
217 });
218 }
219
220 #[test]
221 fn scroll_bottom() {
222 with_view_state(|context| {
223 context.state.scroll_bottom();
224 context.assert_render_action(&["ScrollBottom"]);
225 });
226 }
227
228 #[test]
229 fn scroll_up() {
230 with_view_state(|context| {
231 context.state.scroll_up();
232 context.assert_render_action(&["ScrollUp"]);
233 });
234 }
235
236 #[test]
237 fn scroll_down() {
238 with_view_state(|context| {
239 context.state.scroll_down();
240 context.assert_render_action(&["ScrollDown"]);
241 });
242 }
243
244 #[test]
245 fn scroll_left() {
246 with_view_state(|context| {
247 context.state.scroll_left();
248 context.assert_render_action(&["ScrollLeft"]);
249 });
250 }
251
252 #[test]
253 fn scroll_right() {
254 with_view_state(|context| {
255 context.state.scroll_right();
256 context.assert_render_action(&["ScrollRight"]);
257 });
258 }
259
260 #[test]
261 fn scroll_page_up() {
262 with_view_state(|context| {
263 context.state.scroll_page_up();
264 context.assert_render_action(&["PageUp"]);
265 });
266 }
267
268 #[test]
269 fn scroll_page_down() {
270 with_view_state(|context| {
271 context.state.scroll_page_down();
272 context.assert_render_action(&["PageDown"]);
273 });
274 }
275
276 #[test]
277 fn resize() {
278 with_view_state(|context| {
279 context.state.resize(10, 20);
280 context.assert_render_action(&["Resize(10, 20)"]);
281 });
282 }
283
284 #[test]
285 fn render() {
286 with_view_state(|context| {
287 context.state.resize(300, 100);
288 context
289 .state
290 .render(&ViewData::new(|updater| updater.push_line(ViewLine::from("Foo"))));
291 assert_eq!(
292 render_view_line(context.state.render_slice().lock().get_lines().first().unwrap(), None),
293 "{Normal}Foo"
294 );
295 });
296 }
297}