blocking_function/
blocking_function.rs

1// Copyright 2019 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! An example of a blocking function running in another thread. We give
16//! the other thread some data and then we also pass some data back
17//! to the main thread using commands.
18
19// On Windows platform, don't show a console when opening the app.
20#![windows_subsystem = "windows"]
21
22use std::{thread, time};
23
24use druid::widget::prelude::*;
25use druid::widget::{Button, Either, Flex, Label, Spinner};
26use druid::{
27    AppDelegate, AppLauncher, Command, Data, DelegateCtx, ExtEventSink, Handled, Lens,
28    LocalizedString, Selector, Target, WidgetExt, WindowDesc,
29};
30
31const FINISH_SLOW_FUNCTION: Selector<u32> = Selector::new("finish_slow_function");
32
33#[derive(Clone, Default, Data, Lens)]
34struct AppState {
35    processing: bool,
36    value: u32,
37}
38
39fn ui_builder() -> impl Widget<AppState> {
40    let button = Button::new("Start slow increment")
41        .on_click(|ctx, data: &mut AppState, _env| {
42            data.processing = true;
43            // In order to make sure that the other thread can communicate with the main thread we
44            // have to pass an external handle to the second thread.
45            // Using this handle we can send commands back to the main thread.
46            wrapped_slow_function(ctx.get_external_handle(), data.value);
47        })
48        .padding(5.0);
49
50    let button_placeholder = Flex::column()
51        .with_child(Label::new(LocalizedString::new("Processing...")).padding(5.0))
52        .with_child(Spinner::new());
53
54    // Hello-counter is defined in the built-in localisation file. This maps to "Current value is {count}"
55    // localised in english, french, or german. Every time the value is updated it shows the new value.
56    let text = LocalizedString::new("hello-counter")
57        .with_arg("count", |data: &AppState, _env| (data.value).into());
58    let label = Label::new(text).padding(5.0).center();
59
60    let either = Either::new(|data, _env| data.processing, button_placeholder, button);
61
62    Flex::column().with_child(label).with_child(either)
63}
64
65fn wrapped_slow_function(sink: ExtEventSink, number: u32) {
66    thread::spawn(move || {
67        let number = slow_function(number);
68        // Once the slow function is done we can use the event sink (the external handle).
69        // This sends the `FINISH_SLOW_FUNCTION` command to the main thread and attach
70        // the number as payload.
71        sink.submit_command(FINISH_SLOW_FUNCTION, number, Target::Auto)
72            .expect("command failed to submit");
73    });
74}
75
76// Pretend this is downloading a file, or doing heavy calculations...
77fn slow_function(number: u32) -> u32 {
78    let a_while = time::Duration::from_millis(2000);
79    thread::sleep(a_while);
80    number + 1
81}
82
83struct Delegate;
84
85impl AppDelegate<AppState> for Delegate {
86    fn command(
87        &mut self,
88        _ctx: &mut DelegateCtx,
89        _target: Target,
90        cmd: &Command,
91        data: &mut AppState,
92        _env: &Env,
93    ) -> Handled {
94        if let Some(number) = cmd.get(FINISH_SLOW_FUNCTION) {
95            // If the command we received is `FINISH_SLOW_FUNCTION` handle the payload.
96            data.processing = false;
97            data.value = *number;
98            Handled::Yes
99        } else {
100            Handled::No
101        }
102    }
103}
104
105fn main() {
106    let main_window =
107        WindowDesc::new(ui_builder()).title(LocalizedString::new("Blocking functions"));
108    AppLauncher::with_window(main_window)
109        .delegate(Delegate {})
110        .log_to_console()
111        .launch(AppState::default())
112        .expect("launch failed");
113}