summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcos <cos>2024-06-05 19:46:15 +0200
committercos <cos>2024-06-05 19:46:15 +0200
commit89052221c847f8f4e24ae1f5bd37759a60d0e173 (patch)
tree29c55de79b6b5a05f6bb748f89a95ce361ab664f
downloadccc-topic/design-poc.zip
demo: Proof-of-concept, dual-thread designtopic/design-poc
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml8
-rw-r--r--src/main.rs170
3 files changed, 180 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1b72444
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/Cargo.lock
+/target
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..863b742
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "ccc"
+version = "0.0.1"
+edition = "2021"
+
+[dependencies]
+crossbeam-channel = "0.5.13"
+cursive = "0.20.0"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..13d24d6
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,170 @@
+use {
+ crossbeam_channel::{
+ Receiver,
+ Sender,
+
+ unbounded,
+ },
+ cursive::{
+ Cursive,
+ CursiveExt,
+ Printer,
+ Rect,
+ View,
+ XY,
+
+ direction::Direction,
+ event::{
+ Event,
+ EventResult,
+ },
+ view::{
+ CannotFocus,
+ Nameable,
+ Selector,
+ ViewNotFound,
+ },
+ views::{
+ Button,
+ LinearLayout,
+ TextView,
+ }
+ },
+ std::{
+ time::Duration,
+ thread::{
+ self,
+ sleep,
+ },
+ }
+};
+
+type CursiveSender = Sender<Box<(dyn for<'a> FnOnce(&'a mut Cursive) + Send + 'static)>>;
+
+struct DataView {
+ data: Option<String>,
+ view: TextView,
+}
+
+impl DataView {
+ pub fn new() -> Self {
+ Self {
+ data: None,
+ view: TextView::new("uninitialized"),
+ }
+ }
+
+ fn set_data(&mut self, data: &str) {
+ self.data = Some(data.to_owned());
+ self.view.set_content(data.to_string())
+ }
+}
+
+impl View for DataView {
+ fn draw(&self, printer: &Printer<'_, '_>) {
+ self.view.draw(printer)
+ }
+
+ fn layout(&mut self, xy: XY<usize>) {
+ self.view.layout(xy)
+ }
+
+ fn needs_relayout(&self) -> bool {
+ self.view.needs_relayout()
+ }
+
+ fn required_size(&mut self, constraint: XY<usize>) -> XY<usize> {
+ self.view.required_size(constraint)
+ }
+
+ fn on_event(&mut self, event: Event) -> EventResult {
+ self.view.on_event(event)
+ }
+
+ // fn call_on_any(), should not be needed for this view.
+
+ fn focus_view(&mut self, selector: &Selector<'_>) -> Result<EventResult, ViewNotFound> {
+ self.view.focus_view(selector)
+ }
+
+ fn take_focus( &mut self, source: Direction) -> Result<EventResult, CannotFocus> {
+ self.view.take_focus(source)
+ }
+
+ fn important_area(&self, view_size: XY<usize>) -> Rect {
+ self.view.important_area(view_size)
+ }
+
+ fn type_name(&self) -> &'static str {
+ "DataView"
+ }
+}
+
+fn data_request(cur: &mut Cursive) {
+ if let Some(sender) = cur.user_data::<Sender<bool>>() {
+ let _ = sender.send(true);
+ }
+}
+
+fn populate_data(s: &mut Cursive, is_data: bool) {
+ if let Some(mut data_view) = s.find_name::<DataView>("populate_me") {
+ if is_data {
+ data_view.set_data("populated")
+ } else {
+ data_view.set_data("initialized")
+ }
+ }
+}
+
+fn background_thread(reciever: Receiver<bool>, sender: CursiveSender) {
+ while let Ok(msg) = reciever.recv() {
+ eprintln!("Received a message. Sleeping 3 seconds before sending response.");
+ sleep(Duration::from_secs(3));
+ let _ = sender.send(Box::new(move |s| populate_data(s, msg)));
+ }
+}
+
+fn main() {
+ // In order to maintain the main thread responsive in dealing with the user interface, we wish
+ // to offload all server communication to a background thread. This code demonstrates how to
+ // send a message from the ui, process it in the background and send the result back for the
+ // user interface to update the content of its view.
+
+ // Firstly, we need a channel pair for sending a message from the ui to the worker thread.
+ let (wrk_sender, wrk_receiver) = unbounded::<bool>();
+
+ // We then construct the actual user interface. Our DataView, a button to trigger populating
+ // it and a button to quit the app.
+ let mut app = Cursive::new();
+ let mut horizontal = LinearLayout::horizontal();
+ let mut vertical = LinearLayout::vertical();
+ let data_view = DataView::new().with_name("populate_me");
+ let populate_button = Button::new("data", data_request);
+ let quit_button = Button::new("quit", |s| s.quit());
+ vertical.add_child(data_view);
+ horizontal.add_child(populate_button);
+ horizontal.add_child(quit_button);
+ vertical.add_child(horizontal);
+ app.add_layer(vertical);
+
+ // Once having created the app, we send a request to initialize our view.
+ let _ = wrk_sender.send(false);
+
+ // We then place the sender in the user_data, to make it available for any ui function.
+ app.set_user_data(wrk_sender);
+
+ // Getting the callback sender from the user interface, needed for the worker to respond.
+ let ui_sender = app.cb_sink().clone();
+
+ // Then we launch the background worker thread, providing it the appropriate channel ends.
+ let _worker = thread::spawn(move || background_thread(wrk_receiver, ui_sender));
+
+ // Lastly we launch the user interface.
+ app.run();
+
+ // Three seconds after launching, the text changes from "uninitialized" to "initialized" and
+ // further, three seconds after pressing the <data> button the text becomes "populated".
+ //
+ // Essentially, this design should work for the complete program. Yet with some slightly more
+ // expressive choice of datatypes than merely a bool.
+}