invalidation/
invalidation.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//! Demonstrates how to debug invalidation regions, and also shows the
16//! invalidation behavior of several built-in widgets.
17
18// On Windows platform, don't show a console when opening the app.
19#![windows_subsystem = "windows"]
20
21use druid::im::Vector;
22use druid::kurbo::{self, Shape};
23use druid::widget::prelude::*;
24use druid::widget::{Button, Flex, Scroll, Split, TextBox};
25use druid::{AppLauncher, Color, Data, Lens, LocalizedString, Point, WidgetExt, WindowDesc};
26
27use instant::Instant;
28
29pub fn main() {
30    let window = WindowDesc::new(build_widget()).title(
31        LocalizedString::new("invalidate-demo-window-title").with_placeholder("Invalidate demo"),
32    );
33    let state = AppState {
34        label: "My label".into(),
35        circles: Vector::new(),
36    };
37    AppLauncher::with_window(window)
38        .log_to_console()
39        .launch(state)
40        .expect("launch failed");
41}
42
43#[derive(Clone, Data, Lens)]
44struct AppState {
45    label: String,
46    circles: Vector<Circle>,
47}
48
49fn build_widget() -> impl Widget<AppState> {
50    let mut col = Flex::column();
51    col.add_child(TextBox::new().lens(AppState::label).padding(3.0));
52    for i in 0..30 {
53        col.add_child(Button::new(format!("Button {i}")).padding(3.0));
54    }
55    Split::columns(Scroll::new(col), CircleView.lens(AppState::circles)).debug_invalidation()
56}
57
58struct CircleView;
59
60#[derive(Clone, Data)]
61struct Circle {
62    pos: Point,
63    #[data(eq)]
64    time: Instant,
65}
66
67const RADIUS: f64 = 25.0;
68
69impl Widget<Vector<Circle>> for CircleView {
70    fn event(&mut self, ctx: &mut EventCtx, ev: &Event, data: &mut Vector<Circle>, _env: &Env) {
71        if let Event::MouseDown(ev) = ev {
72            if ev.mods.shift() {
73                data.push_back(Circle {
74                    pos: ev.pos,
75                    time: Instant::now(),
76                });
77            } else if ev.mods.ctrl() {
78                data.retain(|c| {
79                    if (c.pos - ev.pos).hypot() > RADIUS {
80                        true
81                    } else {
82                        ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
83                        false
84                    }
85                });
86            } else {
87                // Move the circle to a new location, invalidating the old locations. The new location
88                // will be invalidated during AnimFrame.
89                for c in data.iter() {
90                    ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
91                }
92                data.clear();
93                data.push_back(Circle {
94                    pos: ev.pos,
95                    time: Instant::now(),
96                });
97            }
98            ctx.request_anim_frame();
99        } else if let Event::AnimFrame(_) = ev {
100            for c in &*data {
101                ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
102            }
103            if !data.is_empty() {
104                ctx.request_anim_frame();
105            }
106        }
107    }
108
109    fn lifecycle(
110        &mut self,
111        _ctx: &mut LifeCycleCtx,
112        _ev: &LifeCycle,
113        _data: &Vector<Circle>,
114        _env: &Env,
115    ) {
116    }
117
118    fn update(
119        &mut self,
120        _ctx: &mut UpdateCtx,
121        _old: &Vector<Circle>,
122        _new: &Vector<Circle>,
123        _env: &Env,
124    ) {
125    }
126
127    fn layout(
128        &mut self,
129        _ctx: &mut LayoutCtx,
130        bc: &BoxConstraints,
131        _data: &Vector<Circle>,
132        _env: &Env,
133    ) -> Size {
134        bc.max()
135    }
136
137    fn paint(&mut self, ctx: &mut PaintCtx, data: &Vector<Circle>, _env: &Env) {
138        ctx.with_save(|ctx| {
139            let rect = ctx.size().to_rect();
140            ctx.clip(rect);
141            ctx.fill(rect, &Color::WHITE);
142            let now = Instant::now();
143            for c in data {
144                let color =
145                    Color::BLACK.with_alpha(now.duration_since(c.time).as_secs_f64().cos().abs());
146                ctx.fill(kurbo::Circle::new(c.pos, RADIUS), &color);
147            }
148        });
149    }
150}