view/thread/
state.rs

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/// Represents a message sender and receiver for passing actions between threads.
15#[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	/// Create a new instance.
26	#[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	/// Clone the render slice.
56	#[inline]
57	#[must_use]
58	pub fn render_slice(&self) -> Arc<Mutex<RenderSlice>> {
59		Arc::clone(&self.render_slice)
60	}
61
62	/// Queue a start action.
63	///
64	/// # Errors
65	/// Results in an error if the sender has been closed.
66	#[inline]
67	pub fn start(&self) {
68		self.paused.store(false, Ordering::Release);
69		self.send_update(ViewAction::Start);
70	}
71
72	/// Pause the event read thread.
73	///
74	/// # Errors
75	/// Results in an error if the sender has been closed.
76	#[inline]
77	pub fn stop(&self) {
78		self.paused.store(true, Ordering::Release);
79		self.send_update(ViewAction::Stop);
80	}
81
82	/// Queue an end action.
83	///
84	/// # Errors
85	/// Results in an error if the sender has been closed.
86	#[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	/// Queue a refresh action.
94	///
95	/// # Errors
96	/// Results in an error if the sender has been closed.
97	#[inline]
98	pub fn refresh(&self) {
99		self.send_update(ViewAction::Refresh);
100	}
101
102	/// Queue a scroll up action.
103	#[inline]
104	pub fn scroll_top(&self) {
105		self.render_slice.lock().borrow_mut().record_scroll_top();
106	}
107
108	/// Queue a scroll up action.
109	#[inline]
110	pub fn scroll_bottom(&self) {
111		self.render_slice.lock().borrow_mut().record_scroll_bottom();
112	}
113
114	/// Queue a scroll up action.
115	#[inline]
116	pub fn scroll_up(&self) {
117		self.render_slice.lock().borrow_mut().record_scroll_up();
118	}
119
120	/// Queue a scroll down action.
121	#[inline]
122	pub fn scroll_down(&self) {
123		self.render_slice.lock().borrow_mut().record_scroll_down();
124	}
125
126	/// Queue a scroll left action.
127	#[inline]
128	pub fn scroll_left(&self) {
129		self.render_slice.lock().borrow_mut().record_scroll_left();
130	}
131
132	/// Queue a scroll right action.
133	#[inline]
134	pub fn scroll_right(&self) {
135		self.render_slice.lock().borrow_mut().record_scroll_right();
136	}
137
138	/// Queue a scroll up a page action.
139	#[inline]
140	pub fn scroll_page_up(&self) {
141		self.render_slice.lock().borrow_mut().record_page_up();
142	}
143
144	/// Queue a scroll down a page action.
145	#[inline]
146	pub fn scroll_page_down(&self) {
147		self.render_slice.lock().borrow_mut().record_page_down();
148	}
149
150	/// Queue a resize action.
151	#[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	/// Sync the `ViewData` and queue a render action.
160	///
161	/// # Errors
162	/// Results in an error if the sender has been closed.
163	#[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}