#![windows_subsystem = "windows"]
use druid::im::Vector;
use druid::kurbo::{self, Shape};
use druid::widget::prelude::*;
use druid::widget::{Button, Flex, Scroll, Split, TextBox};
use druid::{AppLauncher, Color, Data, Lens, LocalizedString, Point, WidgetExt, WindowDesc};
use instant::Instant;
pub fn main() {
let window = WindowDesc::new(build_widget()).title(
LocalizedString::new("invalidate-demo-window-title").with_placeholder("Invalidate demo"),
);
let state = AppState {
label: "My label".into(),
circles: Vector::new(),
};
AppLauncher::with_window(window)
.log_to_console()
.launch(state)
.expect("launch failed");
}
#[derive(Clone, Data, Lens)]
struct AppState {
label: String,
circles: Vector<Circle>,
}
fn build_widget() -> impl Widget<AppState> {
let mut col = Flex::column();
col.add_child(TextBox::new().lens(AppState::label).padding(3.0));
for i in 0..30 {
col.add_child(Button::new(format!("Button {i}")).padding(3.0));
}
Split::columns(Scroll::new(col), CircleView.lens(AppState::circles)).debug_invalidation()
}
struct CircleView;
#[derive(Clone, Data)]
struct Circle {
pos: Point,
#[data(eq)]
time: Instant,
}
const RADIUS: f64 = 25.0;
impl Widget<Vector<Circle>> for CircleView {
fn event(&mut self, ctx: &mut EventCtx, ev: &Event, data: &mut Vector<Circle>, _env: &Env) {
if let Event::MouseDown(ev) = ev {
if ev.mods.shift() {
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
} else if ev.mods.ctrl() {
data.retain(|c| {
if (c.pos - ev.pos).hypot() > RADIUS {
true
} else {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
false
}
});
} else {
for c in data.iter() {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
data.clear();
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
}
ctx.request_anim_frame();
} else if let Event::AnimFrame(_) = ev {
for c in &*data {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
if !data.is_empty() {
ctx.request_anim_frame();
}
}
}
fn lifecycle(
&mut self,
_ctx: &mut LifeCycleCtx,
_ev: &LifeCycle,
_data: &Vector<Circle>,
_env: &Env,
) {
}
fn update(
&mut self,
_ctx: &mut UpdateCtx,
_old: &Vector<Circle>,
_new: &Vector<Circle>,
_env: &Env,
) {
}
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &Vector<Circle>,
_env: &Env,
) -> Size {
bc.max()
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &Vector<Circle>, _env: &Env) {
ctx.with_save(|ctx| {
let rect = ctx.size().to_rect();
ctx.clip(rect);
ctx.fill(rect, &Color::WHITE);
let now = Instant::now();
for c in data {
let color =
Color::BLACK.with_alpha(now.duration_since(c.time).as_secs_f64().cos().abs());
ctx.fill(kurbo::Circle::new(c.pos, RADIUS), &color);
}
});
}
}