summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 13d24d6291fe6e195c3b66ba5296064d67eadb12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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.
}