use druid::{
kurbo::{BezPath, Shape},
piet::{PaintBrush, TextStorage},
widget::{prelude::*, Click, ControllerHost},
Affine, Data,
};
use tracing::warn;
use crate::theme::{color as theme, icons::IconKeyPair};
type DynamicIconCallback<T> = Option<Box<dyn Fn(&T, &Env) -> IconKeyPair>>;
pub struct IconButton<T> {
icon_key: IconKeyPair,
icon_dyn: DynamicIconCallback<T>,
icon_path: BezPath,
flat: bool,
accent: bool,
}
impl<T: Data> IconButton<T> {
pub fn dynamic(icon_key: impl Fn(&T, &Env) -> IconKeyPair + 'static) -> IconButton<T> {
IconButton {
icon_key: crate::theme::icons::EMPTY,
icon_dyn: Some(Box::new(icon_key)),
icon_path: BezPath::new(),
accent: false,
flat: false,
}
}
pub fn new(icon_key: IconKeyPair) -> IconButton<T> {
IconButton {
icon_key,
icon_dyn: None,
icon_path: BezPath::new(),
accent: false,
flat: false,
}
}
pub fn on_click(
self,
f: impl Fn(&mut EventCtx, &mut T, &Env) + 'static,
) -> ControllerHost<Self, Click<T>> {
ControllerHost::new(self, Click::new(f))
}
pub fn set_accent(&mut self, value: bool) {
self.accent = value;
}
pub fn with_accent(mut self, value: bool) -> Self {
self.set_accent(value);
self
}
pub fn set_flat(&mut self, value: bool) {
self.flat = value;
}
pub fn with_flat(mut self, value: bool) -> Self {
self.set_flat(value);
self
}
fn reload_icon(&mut self, env: &Env) {
let key = env.get(&self.icon_key.0);
let svg_path = key.as_str();
match BezPath::from_svg(svg_path) {
Ok(path) => {
self.icon_path = path;
}
Err(err) => {
warn!("无法读取 SVG 填充路径字符串 {svg_path:?}: {err:?}");
}
}
}
}
impl<T: Data> Widget<T> for IconButton<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
match event {
Event::MouseDown(_) => {
ctx.set_active(true);
ctx.request_paint();
}
Event::MouseUp(_) => {
if ctx.is_active() {
ctx.set_active(false);
ctx.request_paint();
}
}
_ => (),
}
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
if let LifeCycle::HotChanged(_) = event {
ctx.request_paint();
} else if let LifeCycle::WidgetAdded = event {
if let Some(icon_callback) = &self.icon_dyn {
self.icon_key = icon_callback(data, env);
}
self.reload_icon(env);
}
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
if let Some(icon_callback) = &self.icon_dyn {
if !data.same(old_data) || ctx.env_changed() {
self.icon_key = icon_callback(data, env);
self.reload_icon(env);
}
} else if ctx.env_key_changed(&self.icon_key.0) {
self.reload_icon(env);
}
}
fn layout(&mut self, _: &mut LayoutCtx, bc: &BoxConstraints, _: &T, _: &Env) -> Size {
bc.debug_check("IconButton");
bc.constrain((32., 32.))
}
fn paint(&mut self, ctx: &mut PaintCtx, _: &T, env: &Env) {
let is_hot = ctx.is_hot();
let is_active = ctx.is_active();
let is_disabled = ctx.is_disabled();
let size = ctx.size();
super::common::print_common_button(
(self.accent, self.flat, is_active, is_hot, is_disabled),
ctx,
size,
env,
);
ctx.with_save(move |ctx| {
ctx.transform(Affine::translate(
((size - self.icon_path.bounding_box().size()) / 2.).to_vec2(),
));
ctx.fill_even_odd(
&self.icon_path,
&if self.accent {
PaintBrush::Color(env.get(theme::chrome::WHITE))
} else {
PaintBrush::Color(env.get(theme::base::HIGH))
},
)
});
}
}