Files
anim
async_event
blocking_function
calc
cursor
custom_widget
disabled
druid
druid_derive
druid_shell
edit_text
either
event_viewer
flex
game_of_life
hello
identity
image
invalidate
invalidation
layout
lens
list
markdown_preview
multiwin
open_save
panels
perftest
quit
scroll
scroll_colors
shello
split_demo
styled_text
sub_window
switches
tabs
text
textbox
timer
transparency
view_switcher
  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
// Copyright 2020 The Druid Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! An example of sending commands from another thread.
//! This is useful when you want to have some kind of
//! generated content (like here), or some task that just
//! takes a long time but don't want to block the main thread
//! (waiting on an http request, some cpu intensive work etc.)

// On Windows platform, don't show a console when opening the app.
#![windows_subsystem = "windows"]

use instant::Instant;
use std::thread;
use std::time::Duration;

use druid::widget::prelude::*;
use druid::{AppLauncher, Color, Selector, Target, WidgetExt, WindowDesc};

// If you want to submit commands to an event sink you have to give it some kind
// of ID. The selector is that, it also assures the accompanying data-type is correct.
// look at the docs for `Selector` for more detail.
const SET_COLOR: Selector<Color> = Selector::new("event-example.set-color");

pub fn main() {
    let window = WindowDesc::new(make_ui()).title("External Event Demo");

    let launcher = AppLauncher::with_window(window);

    // If we want to create commands from another thread `launcher.get_external_handle()`
    // should be used. For sending commands from within widgets you can always call
    // `ctx.submit_command`
    let event_sink = launcher.get_external_handle();
    // We create a new thread and generate colours in it.
    // This happens on a second thread so that we can run the UI in the
    // main thread. Generating some colours nicely follows the pattern for what
    // should be done like this: generating something over time
    // (like this or reacting to external events), or something that takes a
    // long time and shouldn't block main UI updates.
    thread::spawn(move || generate_colors(event_sink));

    launcher
        .log_to_console()
        .launch(Color::BLACK)
        .expect("launch failed");
}

fn generate_colors(event_sink: druid::ExtEventSink) {
    // This function is called in a separate thread, and runs until the program ends.
    // We take an `ExtEventSink` as an argument, we can use this event sink to send
    // commands to the main thread. Every time we generate a new colour we send it
    // to the main thread.
    let start_time = Instant::now();
    let mut color = Color::WHITE;

    loop {
        let time_since_start = (Instant::now() - start_time).as_nanos();
        let (r, g, b, _) = color.as_rgba8();

        // there is no logic here; it's a very silly way of mutating the color.
        color = match (time_since_start % 2, time_since_start % 3) {
            (0, _) => Color::rgb8(r.wrapping_add(3), g, b),
            (_, 0) => Color::rgb8(r, g.wrapping_add(3), b),
            (_, _) => Color::rgb8(r, g, b.wrapping_add(3)),
        };

        // We send a command to the event_sink. This command will be
        // send to the widgets, and widgets or controllers can look for this
        // event. We give it the data associated with the event and a target.
        // In this case this is just `Target::Auto`, look at the identity example
        // for more detail on how to send commands to specific widgets.
        if event_sink
            .submit_command(SET_COLOR, color.clone(), Target::Auto)
            .is_err()
        {
            break;
        }
        thread::sleep(Duration::from_millis(20));
    }
}

/// A widget that displays a color.
struct ColorWell;

impl Widget<Color> for ColorWell {
    fn event(&mut self, _ctx: &mut EventCtx, event: &Event, data: &mut Color, _env: &Env) {
        match event {
            // This is where we handle our command.
            Event::Command(cmd) if cmd.is(SET_COLOR) => {
                // We don't do much data processing in the `event` method.
                // All we really do is just set the data. This causes a call
                // to `update` which requests a paint. You can also request a paint
                // during the event, but this should be reserved for changes to self.
                // For changes to `Data` always make `update` do the paint requesting.
                *data = cmd.get_unchecked(SET_COLOR).clone();
            }
            _ => (),
        }
    }

    fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &Color, _: &Env) {}

    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &Color, data: &Color, _: &Env) {
        if old_data != data {
            ctx.request_paint()
        }
    }

    fn layout(&mut self, _: &mut LayoutCtx, bc: &BoxConstraints, _: &Color, _: &Env) -> Size {
        bc.max()
    }

    fn paint(&mut self, ctx: &mut PaintCtx, data: &Color, _env: &Env) {
        let rect = ctx.size().to_rounded_rect(5.0);
        ctx.fill(rect, data);
    }
}

fn make_ui() -> impl Widget<Color> {
    ColorWell
        .fix_width(300.0)
        .fix_height(300.0)
        .padding(10.0)
        .center()
}