#![windows_subsystem = "windows"]
use druid::{
im,
kurbo::{Affine, BezPath, Circle, Point},
piet::{FixedLinearGradient, GradientStop, InterpolationMode},
widget::{
prelude::*, Button, Checkbox, FillStrat, Flex, Image, Label, List, Painter, ProgressBar,
RadioGroup, Scroll, Slider, Spinner, Stepper, Switch, TextBox,
},
AppLauncher, Color, Data, ImageBuf, Lens, Widget, WidgetExt, WidgetPod, WindowDesc,
};
#[cfg(feature = "svg")]
use druid::widget::{Svg, SvgData};
const XI_IMAGE: &[u8] = include_bytes!("assets/xi.image");
#[derive(Clone, Data, Lens)]
struct AppData {
label_data: String,
checkbox_data: bool,
clicked_count: u64,
list_items: im::Vector<String>,
progressbar: f64,
radio: MyRadio,
stepper: f64,
editable_text: String,
}
#[derive(Clone, Data, PartialEq)]
enum MyRadio {
GaGa,
GuGu,
BaaBaa,
}
pub fn main() {
let main_window = WindowDesc::new(ui_builder()).title("Widget Gallery");
let data = AppData {
label_data: "test".into(),
checkbox_data: false,
clicked_count: 0,
list_items: im::vector!["1".into(), "2".into(), "3".into()],
progressbar: 0.5,
radio: MyRadio::GaGa,
stepper: 0.0,
editable_text: "edit me!".into(),
};
AppLauncher::with_window(main_window)
.log_to_console()
.launch(data)
.expect("launch failed");
}
fn ui_builder() -> impl Widget<AppData> {
#[cfg(feature = "svg")]
let svg_example = label_widget(
Svg::new(
include_str!("./assets/tiger.svg")
.parse::<SvgData>()
.unwrap(),
),
"Svg",
);
#[cfg(not(feature = "svg"))]
let svg_example = label_widget(Label::new("SVG not supported (yet)").center(), "Svg");
Scroll::new(
SquaresGrid::new()
.with_cell_size(Size::new(200.0, 240.0))
.with_spacing(20.0)
.with_child(label_widget(
Label::new(|data: &AppData, _: &_| data.label_data.clone()),
"Label",
))
.with_child(label_widget(
Flex::column()
.with_child(
Button::new("Click me!")
.on_click(|_, data: &mut AppData, _: &_| data.clicked_count += 1),
)
.with_spacer(4.0)
.with_child(Label::new(|data: &AppData, _: &_| {
format!("Clicked {} times!", data.clicked_count)
})),
"Button",
))
.with_child(label_widget(
Checkbox::new("Check me!").lens(AppData::checkbox_data),
"Checkbox",
))
.with_child(label_widget(
List::new(|| {
Label::new(|data: &String, _: &_| format!("List item: {}", data))
.center()
.background(Color::hlc(230.0, 50.0, 50.0))
.fix_height(40.0)
.expand_width()
})
.lens(AppData::list_items),
"List",
))
.with_child(label_widget(
Flex::column()
.with_child(ProgressBar::new().lens(AppData::progressbar))
.with_spacer(4.0)
.with_child(Label::new(|data: &AppData, _: &_| {
format!("{:.1}%", data.progressbar * 100.0)
}))
.with_spacer(4.0)
.with_child(
Flex::row()
.with_child(Button::new("<<").on_click(|_, data: &mut AppData, _| {
data.progressbar = (data.progressbar - 0.05).max(0.0);
}))
.with_spacer(4.0)
.with_child(Button::new(">>").on_click(|_, data: &mut AppData, _| {
data.progressbar = (data.progressbar + 0.05).min(1.0);
})),
),
"ProgressBar",
))
.with_child(label_widget(
Painter::new(paint_example).fix_size(32.0, 32.0),
"Painter",
))
.with_child(label_widget(
RadioGroup::column(vec![
("radio gaga", MyRadio::GaGa),
("radio gugu", MyRadio::GuGu),
("radio baabaa", MyRadio::BaaBaa),
])
.lens(AppData::radio),
"RadioGroup",
))
.with_child(label_widget(
Flex::column()
.with_child(
Slider::new()
.with_range(0.05, 0.95)
.with_step(0.10)
.lens(AppData::progressbar),
)
.with_spacer(4.0)
.with_child(Label::new(|data: &AppData, _: &_| {
format!("{:3.2}%", data.progressbar * 100.)
})),
"Slider",
))
.with_child(label_widget(
Flex::row()
.with_child(Stepper::new().lens(AppData::stepper))
.with_spacer(4.0)
.with_child(Label::new(|data: &AppData, _: &_| {
format!("{:.1}", data.stepper)
})),
"Stepper",
))
.with_child(label_widget(
TextBox::new().lens(AppData::editable_text),
"TextBox",
))
.with_child(label_widget(
Switch::new().lens(AppData::checkbox_data),
"Switch",
))
.with_child(label_widget(
Spinner::new().fix_height(40.0).center(),
"Spinner",
))
.with_child(label_widget(
Image::new(
ImageBuf::from_data(include_bytes!("./assets/PicWithAlpha.png")).unwrap(),
)
.fill_mode(FillStrat::Fill)
.interpolation_mode(InterpolationMode::Bilinear),
"Image",
))
.with_child(svg_example),
)
.vertical()
}
fn label_widget<T: Data>(widget: impl Widget<T> + 'static, label: &str) -> impl Widget<T> {
Flex::column()
.must_fill_main_axis(true)
.with_flex_child(widget.center(), 1.0)
.with_child(
Painter::new(|ctx, _: &_, _: &_| {
let size = ctx.size().to_rect();
ctx.fill(size, &Color::WHITE)
})
.fix_height(1.0),
)
.with_child(Label::new(label).center().fix_height(40.0))
.border(Color::WHITE, 1.0)
}
fn load_xi_image<Ctx: druid::RenderContext>(ctx: &mut Ctx) -> Ctx::Image {
ctx.make_image(32, 32, XI_IMAGE, druid::piet::ImageFormat::Rgb)
.unwrap()
}
fn paint_example<T>(ctx: &mut PaintCtx, _: &T, _env: &Env) {
let bounds = ctx.size().to_rect();
let img = load_xi_image(ctx.render_ctx);
ctx.draw_image(
&img,
bounds,
druid::piet::InterpolationMode::NearestNeighbor,
);
ctx.with_save(|ctx| {
ctx.transform(Affine::scale_non_uniform(bounds.width(), bounds.height()));
let i_dot = Circle::new((0.775, 0.18), 0.05);
let i_dot_brush = ctx.solid_brush(Color::WHITE);
ctx.fill(i_dot, &i_dot_brush);
let mut spare = BezPath::new();
spare.move_to((0.1, 0.1));
spare.line_to((0.2, 0.1));
spare.line_to((0.9, 0.9));
spare.line_to((0.8, 0.9));
spare.close_path();
let spare_brush = ctx
.gradient(FixedLinearGradient {
start: (0.0, 0.0).into(),
end: (1.0, 1.0).into(),
stops: vec![
GradientStop {
pos: 0.0,
color: Color::rgb(1.0, 0.0, 0.0),
},
GradientStop {
pos: 1.0,
color: Color::rgb(0.4, 0.0, 0.0),
},
],
})
.unwrap();
ctx.fill(spare, &spare_brush);
});
}
const DEFAULT_GRID_CELL_SIZE: Size = Size::new(100.0, 100.0);
const DEFAULT_GRID_SPACING: f64 = 10.0;
pub struct SquaresGrid<T> {
widgets: Vec<WidgetPod<T, Box<dyn Widget<T>>>>,
drawable_widgets: usize,
cell_size: Size,
spacing: f64,
}
impl<T> SquaresGrid<T> {
pub fn new() -> Self {
SquaresGrid {
widgets: vec![],
drawable_widgets: 0,
cell_size: DEFAULT_GRID_CELL_SIZE,
spacing: DEFAULT_GRID_SPACING,
}
}
pub fn with_spacing(mut self, spacing: f64) -> Self {
self.spacing = spacing;
self
}
pub fn with_cell_size(mut self, cell_size: Size) -> Self {
self.cell_size = cell_size;
self
}
pub fn with_child(mut self, widget: impl Widget<T> + 'static) -> Self {
self.widgets.push(WidgetPod::new(Box::new(widget)));
self
}
}
impl<T> Default for SquaresGrid<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Data> Widget<T> for SquaresGrid<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
for widget in self.widgets.iter_mut() {
widget.event(ctx, event, data, env);
}
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
for widget in self.widgets.iter_mut() {
widget.lifecycle(ctx, event, data, env);
}
}
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
for widget in self.widgets.iter_mut() {
widget.update(ctx, data, env);
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
let count = self.widgets.len() as f64;
let ideal_width = (self.cell_size.width + self.spacing + 1.0) * count;
let width = ideal_width.min(bc.max().width).max(bc.min().width);
let cells_in_row =
((width - self.spacing) / (self.cell_size.width + self.spacing)).floor() as usize;
let (height, rows) = if cells_in_row > 0 {
let mut rows = (count / cells_in_row as f64).ceil() as usize;
let height_from_rows =
|rows: usize| (rows as f64) * (self.cell_size.height + self.spacing) + self.spacing;
let ideal_height = height_from_rows(rows);
let height = ideal_height.max(bc.min().height).min(bc.max().height);
while height_from_rows(rows) > height && rows > 0 {
rows -= 1;
}
(height, rows)
} else {
(bc.min().height, 0)
};
self.drawable_widgets = self.widgets.len().min(rows * cells_in_row);
let mut x_position = self.spacing;
let mut y_position = self.spacing;
for (idx, widget) in self
.widgets
.iter_mut()
.take(self.drawable_widgets)
.enumerate()
{
widget.layout(
ctx,
&BoxConstraints::new(self.cell_size, self.cell_size),
data,
env,
);
widget.set_origin(ctx, Point::new(x_position, y_position));
x_position += self.cell_size.width + self.spacing;
if (idx + 1) % cells_in_row == 0 {
x_position = self.spacing;
y_position += self.cell_size.height + self.spacing;
}
}
Size { width, height }
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
for widget in self.widgets.iter_mut().take(self.drawable_widgets) {
widget.paint(ctx, data, env);
}
}
}