use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::widget::{Click, ControllerHost, Label, LabelText};
use crate::{theme, Affine, Data, Insets, LinearGradient, UnitPoint};
use tracing::{instrument, trace};
const LABEL_INSETS: Insets = Insets::uniform_xy(8., 2.);
pub struct Button<T> {
label: Label<T>,
label_size: Size,
}
impl<T: Data> Button<T> {
pub fn new(text: impl Into<LabelText<T>>) -> Button<T> {
Button::from_label(Label::new(text))
}
pub fn from_label(label: Label<T>) -> Button<T> {
Button {
label,
label_size: Size::ZERO,
}
}
pub fn dynamic(text: impl Fn(&T, &Env) -> String + 'static) -> Self {
let text: LabelText<T> = text.into();
Button::new(text)
}
pub fn on_click(
self,
f: impl Fn(&mut EventCtx, &mut T, &Env) + 'static,
) -> ControllerHost<Self, Click<T>> {
ControllerHost::new(self, Click::new(f))
}
}
impl<T: Data> Widget<T> for Button<T> {
#[instrument(name = "Button", level = "trace", skip(self, ctx, event, _data, _env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
match event {
Event::MouseDown(_) => {
if !ctx.is_disabled() {
ctx.set_active(true);
ctx.request_paint();
trace!("Button {:?} pressed", ctx.widget_id());
}
}
Event::MouseUp(_) => {
if ctx.is_active() && !ctx.is_disabled() {
ctx.request_paint();
trace!("Button {:?} released", ctx.widget_id());
}
ctx.set_active(false);
}
_ => (),
}
}
#[instrument(name = "Button", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
if let LifeCycle::HotChanged(_) | LifeCycle::DisabledChanged(_) = event {
ctx.request_paint();
}
self.label.lifecycle(ctx, event, data, env)
}
#[instrument(name = "Button", level = "trace", skip(self, ctx, old_data, data, env))]
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
self.label.update(ctx, old_data, data, env)
}
#[instrument(name = "Button", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
bc.debug_check("Button");
let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
let label_bc = bc.shrink(padding).loosen();
self.label_size = self.label.layout(ctx, &label_bc, data, env);
let min_height = env.get(theme::BORDERED_WIDGET_HEIGHT);
let baseline = self.label.baseline_offset();
ctx.set_baseline_offset(baseline + LABEL_INSETS.y1);
let button_size = bc.constrain(Size::new(
self.label_size.width + padding.width,
(self.label_size.height + padding.height).max(min_height),
));
trace!("Computed button size: {}", button_size);
button_size
}
#[instrument(name = "Button", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
let is_active = ctx.is_active() && !ctx.is_disabled();
let is_hot = ctx.is_hot();
let size = ctx.size();
let stroke_width = env.get(theme::BUTTON_BORDER_WIDTH);
let rounded_rect = size
.to_rect()
.inset(-stroke_width / 2.0)
.to_rounded_rect(env.get(theme::BUTTON_BORDER_RADIUS));
let bg_gradient = if ctx.is_disabled() {
LinearGradient::new(
UnitPoint::TOP,
UnitPoint::BOTTOM,
(
env.get(theme::DISABLED_BUTTON_LIGHT),
env.get(theme::DISABLED_BUTTON_DARK),
),
)
} else if is_active {
LinearGradient::new(
UnitPoint::TOP,
UnitPoint::BOTTOM,
(env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
)
} else {
LinearGradient::new(
UnitPoint::TOP,
UnitPoint::BOTTOM,
(env.get(theme::BUTTON_LIGHT), env.get(theme::BUTTON_DARK)),
)
};
let border_color = if is_hot && !ctx.is_disabled() {
env.get(theme::BORDER_LIGHT)
} else {
env.get(theme::BORDER_DARK)
};
ctx.stroke(rounded_rect, &border_color, stroke_width);
ctx.fill(rounded_rect, &bg_gradient);
let label_offset = (size.to_vec2() - self.label_size.to_vec2()) / 2.0;
ctx.with_save(|ctx| {
ctx.transform(Affine::translate(label_offset));
self.label.paint(ctx, data, env);
});
}
fn debug_state(&self, _data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
main_value: self.label.text().to_string(),
..Default::default()
}
}
}