invalidation/
invalidation.rs1#![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 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}