pub struct Painter<T>(_);
Expand description
A widget that only handles painting.
This is useful in a situation where layout is controlled elsewhere and you do not need to handle events, but you would like to customize appearance.
When is paint called?
The Painter
widget will call its paint
method anytime its Data
is changed. If you would like it to repaint at other times (such as when
hot or active state changes) you will need to call request_paint
further
up the tree, perhaps in a Controller
widget.
Examples
Changing background color based on some part of data:
use druid::{Env, PaintCtx,Rect, RenderContext};
use druid::widget::Painter;
struct MyData { is_enabled: bool }
let my_painter = Painter::new(|ctx, data: &MyData, env| {
let bounds = ctx.size().to_rect();
if data.is_enabled {
ctx.fill(bounds, &env.get(ENABLED_BG_COLOR));
} else {
ctx.fill(bounds, &env.get(DISABLED_BG_COLOR));
}
});
Using painter to make a simple widget that will draw a selected color
use druid::{Color, Env, PaintCtx,Rect, RenderContext};
use druid::widget::Painter;
const CORNER_RADIUS: f64 = 4.0;
const STROKE_WIDTH: f64 = 2.0;
let colorwell: Painter<Color> = Painter::new(|ctx, data: &Color, env| {
// Shrink the bounds a little, to ensure that our stroke remains within
// the paint bounds.
let bounds = ctx.size().to_rect().inset(-STROKE_WIDTH / 2.0);
let rounded = bounds.to_rounded_rect(CORNER_RADIUS);
ctx.fill(rounded, data);
ctx.stroke(rounded, &env.get(druid::theme::PRIMARY_DARK), STROKE_WIDTH);
});
Implementations§
source§impl<T> Painter<T>
impl<T> Painter<T>
sourcepub fn new(f: impl FnMut(&mut PaintCtx<'_, '_, '_>, &T, &Env) + 'static) -> Self
pub fn new(f: impl FnMut(&mut PaintCtx<'_, '_, '_>, &T, &Env) + 'static) -> Self
Create a new Painter
with the provided paint
fn.
Examples found in repository?
More examples
examples/calc.rs (lines 122-134)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
fn op_button_label(op: char, label: String) -> impl Widget<CalcState> {
let painter = Painter::new(|ctx, _, env| {
let bounds = ctx.size().to_rect();
ctx.fill(bounds, &env.get(theme::PRIMARY_DARK));
if ctx.is_hot() {
ctx.stroke(bounds.inset(-0.5), &Color::WHITE, 1.0);
}
if ctx.is_active() {
ctx.fill(bounds, &env.get(theme::PRIMARY_LIGHT));
}
});
Label::new(label)
.with_text_size(24.)
.center()
.background(painter)
.expand()
.on_click(move |_ctx, data: &mut CalcState, _env| data.op(op))
}
fn op_button(op: char) -> impl Widget<CalcState> {
op_button_label(op, op.to_string())
}
fn digit_button(digit: u8) -> impl Widget<CalcState> {
let painter = Painter::new(|ctx, _, env| {
let bounds = ctx.size().to_rect();
ctx.fill(bounds, &env.get(theme::BACKGROUND_LIGHT));
if ctx.is_hot() {
ctx.stroke(bounds.inset(-0.5), &Color::WHITE, 1.0);
}
if ctx.is_active() {
ctx.fill(bounds, &Color::rgb8(0x71, 0x71, 0x71));
}
});
Label::new(format!("{digit}"))
.with_text_size(24.)
.center()
.background(painter)
.expand()
.on_click(move |_ctx, data: &mut CalcState, _env| data.digit(digit))
}
examples/transparency.rs (lines 64-80)
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
fn build_root_widget() -> impl Widget<HelloState> {
// Draw red circle, and two semi-transparent rectangles
let circle_and_rects = Painter::new(|ctx, _data, _env| {
let boundaries = ctx.size().to_rect();
let center = (boundaries.width() / 2., boundaries.height() / 2.);
let circle = Circle::new(center, center.0.min(center.1));
ctx.fill(circle, &Color::RED);
let rect1 = Rect::new(0., 0., boundaries.width() / 2., boundaries.height() / 2.);
ctx.fill(rect1, &Color::rgba8(0x0, 0xff, 0, 125));
let rect2 = Rect::new(
boundaries.width() / 2.,
boundaries.height() / 2.,
boundaries.width(),
boundaries.height(),
);
ctx.fill(rect2, &Color::rgba8(0x0, 0x0, 0xff, 125));
});
// This textbox modifies the label, idea here is to test that the background
// invalidation works when you type to the textbox
let textbox = TextBox::new()
.with_placeholder("Type to test clearing")
.with_text_size(18.0)
.lens(HelloState::name)
.fix_width(250.);
let label = Label::new(|data: &HelloState, _env: &Env| {
if data.name.is_empty() {
"Text: ".to_string()
} else {
format!("Text: {}!", data.name)
}
})
.with_text_color(Color::RED)
.with_text_size(32.0);
Flex::column()
.with_flex_child(circle_and_rects.expand().controller(DragController), 10.0)
.with_spacer(4.0)
.with_child(textbox)
.with_spacer(4.0)
.with_child(label)
}
examples/panels.rs (lines 39-53)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
fn build_app() -> impl Widget<()> {
let gradient = LinearGradient::new(
UnitPoint::TOP_LEFT,
UnitPoint::BOTTOM_RIGHT,
(DARKER_GREY, LIGHTER_GREY),
);
// a custom background
let polka_dots = Painter::new(|ctx, _, _| {
let bounds = ctx.size().to_rect();
let dot_diam = bounds.width().max(bounds.height()) / 20.;
let dot_spacing = dot_diam * 1.8;
for y in 0..((bounds.height() / dot_diam).ceil() as usize) {
for x in 0..((bounds.width() / dot_diam).ceil() as usize) {
let x_offset = (y % 2) as f64 * (dot_spacing / 2.0);
let x = x as f64 * dot_spacing + x_offset;
let y = y as f64 * dot_spacing;
let circ = Circle::new((x, y), dot_diam / 2.0);
let purp = Color::rgb(1.0, 0.22, 0.76);
ctx.fill(circ, &purp);
}
}
});
Flex::column()
.with_flex_child(
Flex::row()
.with_flex_child(
Label::new("top left")
.center()
.border(DARK_GREY, 4.0)
.padding(10.0),
1.0,
)
.with_flex_child(
Label::new("top right")
.center()
.background(DARK_GREY)
.padding(10.0),
1.0,
),
1.0,
)
.with_flex_child(
Flex::row()
.with_flex_child(
Label::new("bottom left")
.center()
.background(gradient)
.rounded(10.0)
.padding(10.0),
1.0,
)
.with_flex_child(
Label::new("bottom right")
.center()
.border(LIGHTER_GREY, 4.0)
.background(polka_dots)
.rounded(10.0)
.padding(10.0),
1.0,
),
1.0,
)
}
examples/styled_text.rs (lines 73-82)
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
fn ui_builder() -> impl Widget<AppData> {
let my_painter = Painter::new(|ctx, _, _| {
let bounds = ctx.size().to_rect();
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgba8(0, 0, 0, 128));
}
if ctx.is_active() {
ctx.stroke(bounds, &Color::WHITE, 2.0);
}
});
// This is Druid's default text style.
// It's set by theme::LABEL_COLOR and theme::UI_FONT
let label =
Label::new(|data: &String, _env: &_| format!("Default: {data}")).lens(AppData::text);
// The text_color, text_size, and font builder methods can override the
// defaults provided by the theme by passing in a Key or a concrete value.
//
// In this example, text_color receives a Key from the theme, text_size
// gets a custom key which we set with the env_scope wrapper, and the
// default font key (theme::FONT_NAME) is overridden in the env_scope
// wrapper. (Like text_color and text_size, the font can be set using the
// with_font builder method, but overriding here makes it easy to fall back
// to the default font)
let styled_label = Label::new(|data: &AppData, _env: &_| format!("{data}"))
.with_text_color(theme::PRIMARY_LIGHT)
.with_font(MY_CUSTOM_FONT)
.background(my_painter)
.on_click(|_, data, _| {
data.size *= 1.1;
})
.env_scope(|env: &mut druid::Env, data: &AppData| {
let new_font = if data.mono {
FontDescriptor::new(FontFamily::MONOSPACE)
} else {
FontDescriptor::new(FontFamily::SYSTEM_UI)
}
.with_size(data.size);
env.set(MY_CUSTOM_FONT, new_font);
});
let labels = Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(label)
.with_default_spacer()
.with_child(styled_label),
)
.expand_height()
.fix_width(COLUMN_WIDTH);
let stepper = Stepper::new()
.with_range(0.0, 100.0)
.with_step(1.0)
.with_wraparound(false)
.lens(AppData::size);
// TODO: Replace Parse usage with TextBox::with_formatter
#[allow(deprecated)]
let stepper_textbox = LensWrap::new(
Parse::new(TextBox::new()),
AppData::size.map(|x| Some(*x), |x, y| *x = y.unwrap_or(24.0)),
);
let mono_checkbox = Checkbox::new("Monospace").lens(AppData::mono);
let stepper_row = Flex::row()
.with_child(stepper_textbox)
.with_child(stepper)
.with_default_spacer()
.with_child(mono_checkbox);
let input = TextBox::multiline()
.with_placeholder("Your sample text here :)")
.fix_width(COLUMN_WIDTH)
.fix_height(140.0)
.lens(AppData::text);
Flex::column()
.main_axis_alignment(MainAxisAlignment::Center)
.with_default_spacer()
.with_flex_child(labels, 1.0)
.with_default_spacer()
.with_child(input)
.with_default_spacer()
.with_child(stepper_row)
.with_default_spacer()
}
Trait Implementations§
source§impl<T> From<Painter<T>> for BackgroundBrush<T>
impl<T> From<Painter<T>> for BackgroundBrush<T>
source§fn from(src: Painter<T>) -> BackgroundBrush<T>
fn from(src: Painter<T>) -> BackgroundBrush<T>
Converts to this type from the input type.
source§impl<T: Data> Widget<T> for Painter<T>
impl<T: Data> Widget<T> for Painter<T>
source§fn event(&mut self, _: &mut EventCtx<'_, '_>, _: &Event, _: &mut T, _: &Env)
fn event(&mut self, _: &mut EventCtx<'_, '_>, _: &Event, _: &mut T, _: &Env)
Handle an event. Read more
source§fn lifecycle(&mut self, _: &mut LifeCycleCtx<'_, '_>, _: &LifeCycle, _: &T, _: &Env)
fn lifecycle(&mut self, _: &mut LifeCycleCtx<'_, '_>, _: &LifeCycle, _: &T, _: &Env)
Handle a life cycle notification. Read more
source§fn layout(
&mut self,
_ctx: &mut LayoutCtx<'_, '_>,
bc: &BoxConstraints,
_: &T,
_: &Env
) -> Size
fn layout( &mut self, _ctx: &mut LayoutCtx<'_, '_>, bc: &BoxConstraints, _: &T, _: &Env ) -> Size
Compute layout. Read more
source§fn paint(&mut self, ctx: &mut PaintCtx<'_, '_, '_>, data: &T, env: &Env)
fn paint(&mut self, ctx: &mut PaintCtx<'_, '_, '_>, data: &T, env: &Env)
Paint the widget appearance. Read more
source§fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx<'_, '_>,
bc: &BoxConstraints,
data: &T,
env: &Env
) -> f64
fn compute_max_intrinsic( &mut self, axis: Axis, ctx: &mut LayoutCtx<'_, '_>, bc: &BoxConstraints, data: &T, env: &Env ) -> f64
Computes max intrinsic/preferred dimension of a widget on the provided axis. Read more
Auto Trait Implementations§
impl<T> !RefUnwindSafe for Painter<T>
impl<T> !Send for Painter<T>
impl<T> !Sync for Painter<T>
impl<T> Unpin for Painter<T>
impl<T> !UnwindSafe for Painter<T>
Blanket Implementations§
source§impl<T> Instrument for T
impl<T> Instrument for T
source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> RoundFrom<T> for T
impl<T> RoundFrom<T> for T
§fn round_from(x: T) -> T
fn round_from(x: T) -> T
Performs the conversion.
§impl<T, U> RoundInto<U> for Twhere
U: RoundFrom<T>,
impl<T, U> RoundInto<U> for Twhere U: RoundFrom<T>,
§fn round_into(self) -> U
fn round_into(self) -> U
Performs the conversion.