pub trait WidgetExt<T: Data>: Widget<T> + Sized + 'static {
Show 28 methods
// Provided methods
fn padding(self, insets: impl Into<KeyOrValue<Insets>>) -> Padding<T, Self> { ... }
fn center(self) -> Align<T> { ... }
fn align_left(self) -> Align<T> { ... }
fn align_right(self) -> Align<T> { ... }
fn align_vertical(self, align: UnitPoint) -> Align<T> { ... }
fn align_horizontal(self, align: UnitPoint) -> Align<T> { ... }
fn fix_width(self, width: impl Into<KeyOrValue<f64>>) -> SizedBox<T> { ... }
fn fix_height(self, height: impl Into<KeyOrValue<f64>>) -> SizedBox<T> { ... }
fn fix_size(
self,
width: impl Into<KeyOrValue<f64>>,
height: impl Into<KeyOrValue<f64>>
) -> SizedBox<T> { ... }
fn expand(self) -> SizedBox<T> { ... }
fn expand_width(self) -> SizedBox<T> { ... }
fn expand_height(self) -> SizedBox<T> { ... }
fn background(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T> { ... }
fn foreground(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T> { ... }
fn border(
self,
color: impl Into<KeyOrValue<Color>>,
width: impl Into<KeyOrValue<f64>>
) -> Container<T> { ... }
fn env_scope(self, f: impl Fn(&mut Env, &T) + 'static) -> EnvScope<T, Self> { ... }
fn controller<C: Controller<T, Self>>(
self,
controller: C
) -> ControllerHost<Self, C> { ... }
fn on_added(
self,
f: impl Fn(&mut Self, &mut LifeCycleCtx<'_, '_>, &T, &Env) + 'static
) -> ControllerHost<Self, Added<T, Self>> { ... }
fn on_click(
self,
f: impl Fn(&mut EventCtx<'_, '_>, &mut T, &Env) + 'static
) -> ControllerHost<Self, Click<T>> { ... }
fn debug_paint_layout(self) -> EnvScope<T, Self> { ... }
fn debug_widget_id(self) -> EnvScope<T, Self> { ... }
fn debug_invalidation(self) -> DebugInvalidation<T, Self> { ... }
fn debug_widget(self) -> EnvScope<T, Self> { ... }
fn lens<S: Data, L: Lens<S, T>>(self, lens: L) -> LensWrap<S, T, L, Self> { ... }
fn with_id(self, id: WidgetId) -> IdentityWrapper<Self> { ... }
fn boxed(self) -> Box<dyn Widget<T>> { ... }
fn scroll(self) -> Scroll<T, Self> { ... }
fn disabled_if(
self,
disabled_if: impl Fn(&T, &Env) -> bool + 'static
) -> DisabledIf<T, Self> { ... }
}
Expand description
A trait that provides extra methods for combining Widget
s.
Provided Methods§
sourcefn padding(self, insets: impl Into<KeyOrValue<Insets>>) -> Padding<T, Self>
fn padding(self, insets: impl Into<KeyOrValue<Insets>>) -> Padding<T, Self>
Wrap this widget in a Padding
widget with the given Insets
.
Like Padding::new
, this can accept a variety of arguments, including
a Key
referring to Insets
in the Env
.
Examples found in repository?
More examples
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
fn ui_builder() -> impl Widget<AppState> {
// Our UI consists of a column with a button and an `Either` widget
let button = Checkbox::new("Toggle slider")
.lens(AppState::which)
.padding(5.0);
// The `Either` widget has two children, only one of which is visible at a time.
// To determine which child is visible, you pass it a closure that takes the
// `Data` and the `Env` and returns a bool; if it returns `true`, the first
// widget will be visible, and if `false`, the second.
let either = Either::new(
|data, _env| data.which,
Slider::new().lens(AppState::value).padding(5.0),
Label::new("Click to reveal slider").padding(5.0),
);
Flex::column().with_child(button).with_child(either)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let blurb = Label::new(EXPLAINER)
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(blurb)
.with_spacer(24.0)
.with_child(
TextBox::new()
.with_placeholder("Single")
.lens(AppState::single),
)
.with_default_spacer()
.with_flex_child(
TextBox::multiline()
.with_placeholder("Multi")
.lens(AppState::multi)
.expand_width(),
1.0,
)
.padding(8.0)
}
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
fn interactive_area() -> impl Widget<AppState> {
let text_box = TextBox::multiline()
.with_text_color(Color::rgb8(0xf0, 0xf0, 0xea))
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.lens(AppState::text_input)
.controller(EventLogger {
filter: |event| matches!(event, Event::KeyDown(_) | Event::KeyUp(_)),
});
let mouse_box = SizedBox::empty()
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.background(CURSOR_BACKGROUND_COLOR)
.rounded(5.0)
.border(INTERACTIVE_AREA_BORDER, 1.0)
.controller(EventLogger {
filter: |event| {
matches!(
event,
Event::MouseDown(_) | Event::MouseUp(_) | Event::Wheel(_)
)
},
});
Flex::row()
.with_flex_spacer(1.0)
.with_child(text_box)
.with_flex_spacer(1.0)
.with_child(mouse_box)
.with_flex_spacer(1.0)
.padding(10.0)
}
/// The bottom part of the application, a list of received events.
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
sourcefn center(self) -> Align<T>
fn center(self) -> Align<T>
Wrap this widget in an Align
widget, configured to center it.
Examples found in repository?
More examples
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn ui_builder() -> impl Widget<u32> {
let tiger_svg = match include_str!("./assets/tiger.svg").parse::<SvgData>() {
Ok(svg) => svg,
Err(err) => {
error!("{}", err);
error!("Using an empty SVG instead.");
SvgData::default()
}
};
let mut col = Flex::column();
col.add_flex_child(Svg::new(tiger_svg.clone()).fix_width(60.0).center(), 1.0);
col.add_flex_child(Svg::new(tiger_svg.clone()), 1.0);
col.add_flex_child(Svg::new(tiger_svg), 1.0);
col.debug_paint_layout()
}
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))
}
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
fn ui_builder() -> impl Widget<MyComplexState> {
// `TextBox` is of type `Widget<String>`
// via `.lens` we get it to be of type `Widget<MyComplexState>`
let searchbar = TextBox::new().lens(MyComplexState::term_lens);
// `Slider` is of type `Widget<f64>`
// via `.lens` we get it to be of type `Widget<MyComplexState>`
let slider = Slider::new().lens(MyComplexState::scale);
let label = Label::new(|d: &MyComplexState, _: &Env| format!("{}: {:.2}", d.term, d.scale));
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(label)
.with_default_spacer()
.with_child(
Flex::row()
.cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(searchbar)
.with_default_spacer()
.with_child(slider),
)
.center()
}
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
fn ui_builder() -> impl Widget<AppState> {
let button = Button::new("Start slow increment")
.on_click(|ctx, data: &mut AppState, _env| {
data.processing = true;
// In order to make sure that the other thread can communicate with the main thread we
// have to pass an external handle to the second thread.
// Using this handle we can send commands back to the main thread.
wrapped_slow_function(ctx.get_external_handle(), data.value);
})
.padding(5.0);
let button_placeholder = Flex::column()
.with_child(Label::new(LocalizedString::new("Processing...")).padding(5.0))
.with_child(Spinner::new());
// Hello-counter is defined in the built-in localisation file. This maps to "Current value is {count}"
// localised in english, french, or german. Every time the value is updated it shows the new value.
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &AppState, _env| (data.value).into());
let label = Label::new(text).padding(5.0).center();
let either = Either::new(|data, _env| data.processing, button_placeholder, button);
Flex::column().with_child(label).with_child(either)
}
sourcefn align_left(self) -> Align<T>
fn align_left(self) -> Align<T>
Wrap this widget in an Align
widget, configured to align left.
sourcefn align_right(self) -> Align<T>
fn align_right(self) -> Align<T>
Wrap this widget in an Align
widget, configured to align right.
sourcefn align_vertical(self, align: UnitPoint) -> Align<T>
fn align_vertical(self, align: UnitPoint) -> Align<T>
Wrap this widget in an Align
widget, configured to align vertically.
Examples found in repository?
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
fn build_root_widget() -> impl Widget<HelloState> {
// a label that will determine its text based on the current app data.
let label = Label::new(|data: &HelloState, _env: &Env| {
if data.name.is_empty() {
"Hello anybody!?".to_string()
} else {
format!("Hello {}!", data.name)
}
})
.with_text_size(32.0);
// a textbox that modifies `name`.
let textbox = TextBox::new()
.with_placeholder("Who are we greeting?")
.with_text_size(18.0)
.fix_width(TEXT_BOX_WIDTH)
.lens(HelloState::name);
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(label)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(textbox)
.align_vertical(UnitPoint::CENTER)
}
More examples
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 96 97 98 99
fn build_root_widget() -> impl Widget<AppState> {
let range = Flex::row()
.with_child(Label::dynamic(|value: &(f64, f64), _| {
format!("Value Range: {value:?}")
}))
.with_default_spacer()
.with_child(
RangeSlider::new()
.with_range(0.0, 20.0)
.with_step(1.0)
.track_color(KeyOrValue::Concrete(Color::RED))
.fix_width(250.0),
)
.lens(AppState::range);
let value = Flex::row()
.with_child(Label::dynamic(|value: &AppState, _| {
format!("Value: {:?}", value.value)
}))
.with_default_spacer()
.with_child(ViewSwitcher::new(
|data: &AppState, _| data.range,
|range, _, _| {
Slider::new()
.with_range(range.0, range.1)
.track_color(KeyOrValue::Concrete(Color::RED))
.knob_style(KnobStyle::Wedge)
.axis(Axis::Vertical)
.with_step(0.25)
.annotated(1.0, 0.25)
.fix_height(250.0)
.lens(AppState::value)
.boxed()
},
));
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(range)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(value)
.cross_axis_alignment(CrossAxisAlignment::End)
.align_vertical(UnitPoint::RIGHT)
.padding(20.0)
}
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 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
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn align_horizontal(self, align: UnitPoint) -> Align<T>
fn align_horizontal(self, align: UnitPoint) -> Align<T>
Wrap this widget in an Align
widget, configured to align horizontally.
Examples found in repository?
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
fn main_widget() -> impl Widget<AppData> {
Flex::column()
.with_child(named_child("text:", TextBox::new().lens(AppData::text)))
.with_default_spacer()
.with_child(
named_child("text (disabled):", TextBox::new().lens(AppData::text))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(named_child("text:", TextBox::new().lens(AppData::text)))
.with_default_spacer()
.with_child(
named_child("text (disabled):", TextBox::new().lens(AppData::text))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_default_spacer()
.with_child(
named_child(
"value (disabled):",
Slider::new().with_range(0.0, 10.0).lens(AppData::value),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child(
"value (disabled):",
Stepper::new()
.with_range(0.0, 10.0)
.with_step(0.5)
.lens(AppData::value),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child(
"option (disabled):",
Checkbox::new("option").lens(AppData::option),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child("option (disabled):", Switch::new().lens(AppData::option))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
Flex::row()
.with_child(
Button::new("-")
.on_click(|_, data: &mut f64, _| *data -= 1.0)
.disabled_if(|data, _| *data < 1.0),
)
.with_default_spacer()
.with_child(Label::dynamic(|data: &f64, _| data.to_string()))
.with_default_spacer()
.with_child(
Button::new("+")
.on_click(|_, data: &mut f64, _| *data += 1.0)
.disabled_if(|data, _| *data > 9.0),
)
.lens(AppData::value)
.disabled_if(|data: &AppData, _| data.disabled),
)
.with_default_spacer()
.with_default_spacer()
.with_default_spacer()
.with_child(Checkbox::new("disabled").lens(AppData::disabled))
.with_default_spacer()
.cross_axis_alignment(CrossAxisAlignment::End)
.align_horizontal(UnitPoint::CENTER)
}
sourcefn fix_width(self, width: impl Into<KeyOrValue<f64>>) -> SizedBox<T>
fn fix_width(self, width: impl Into<KeyOrValue<f64>>) -> SizedBox<T>
Wrap this widget in a SizedBox
with an explicit width.
Examples found in repository?
More examples
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn ui_builder() -> impl Widget<u32> {
let tiger_svg = match include_str!("./assets/tiger.svg").parse::<SvgData>() {
Ok(svg) => svg,
Err(err) => {
error!("{}", err);
error!("Using an empty SVG instead.");
SvgData::default()
}
};
let mut col = Flex::column();
col.add_flex_child(Svg::new(tiger_svg.clone()).fix_width(60.0).center(), 1.0);
col.add_flex_child(Svg::new(tiger_svg.clone()), 1.0);
col.add_flex_child(Svg::new(tiger_svg), 1.0);
col.debug_paint_layout()
}
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
fn build_root_widget() -> impl Widget<HelloState> {
// a label that will determine its text based on the current app data.
let label = Label::new(|data: &HelloState, _env: &Env| {
if data.name.is_empty() {
"Hello anybody!?".to_string()
} else {
format!("Hello {}!", data.name)
}
})
.with_text_size(32.0);
// a textbox that modifies `name`.
let textbox = TextBox::new()
.with_placeholder("Who are we greeting?")
.with_text_size(18.0)
.fix_width(TEXT_BOX_WIDTH)
.lens(HelloState::name);
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(label)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(textbox)
.align_vertical(UnitPoint::CENTER)
}
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
fn make_spacer_select() -> impl Widget<Params> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Insert Spacers:"))
.with_default_spacer()
.with_child(RadioGroup::column(SPACER_OPTIONS.to_vec()).lens(Params::spacers))
.with_default_spacer()
.with_child(
Flex::row()
.with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(Params::spacer_size)
.fix_width(60.0),
)
.with_spacer(druid::theme::WIDGET_CONTROL_COMPONENT_PADDING)
.with_child(
Stepper::new()
.with_range(2.0, 50.0)
.with_step(2.0)
.lens(Params::spacer_size),
),
)
}
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
/// A single event row.
fn make_list_item() -> impl Widget<LoggedEvent> {
Flex::row()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.number()).fix_width(PROPERTIES[0].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.name()).fix_width(PROPERTIES[1].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.mouse_pos()).fix_width(PROPERTIES[2].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.wheel_delta()).fix_width(PROPERTIES[3].1))
.with_default_spacer()
.with_child(
Label::dynamic(|d: &LoggedEvent, _| d.mouse_button()).fix_width(PROPERTIES[4].1),
)
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.click_count()).fix_width(PROPERTIES[5].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.is_repeat()).fix_width(PROPERTIES[6].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.key()).fix_width(PROPERTIES[7].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.code()).fix_width(PROPERTIES[8].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.modifiers()).fix_width(PROPERTIES[9].1))
.with_default_spacer()
.with_child(Label::dynamic(|d: &LoggedEvent, _| d.location()).fix_width(PROPERTIES[10].1))
}
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 96 97 98 99
fn build_root_widget() -> impl Widget<AppState> {
let range = Flex::row()
.with_child(Label::dynamic(|value: &(f64, f64), _| {
format!("Value Range: {value:?}")
}))
.with_default_spacer()
.with_child(
RangeSlider::new()
.with_range(0.0, 20.0)
.with_step(1.0)
.track_color(KeyOrValue::Concrete(Color::RED))
.fix_width(250.0),
)
.lens(AppState::range);
let value = Flex::row()
.with_child(Label::dynamic(|value: &AppState, _| {
format!("Value: {:?}", value.value)
}))
.with_default_spacer()
.with_child(ViewSwitcher::new(
|data: &AppState, _| data.range,
|range, _, _| {
Slider::new()
.with_range(range.0, range.1)
.track_color(KeyOrValue::Concrete(Color::RED))
.knob_style(KnobStyle::Wedge)
.axis(Axis::Vertical)
.with_step(0.25)
.annotated(1.0, 0.25)
.fix_height(250.0)
.lens(AppState::value)
.boxed()
},
));
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(range)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(value)
.cross_axis_alignment(CrossAxisAlignment::End)
.align_vertical(UnitPoint::RIGHT)
.padding(20.0)
}
sourcefn fix_height(self, height: impl Into<KeyOrValue<f64>>) -> SizedBox<T>
fn fix_height(self, height: impl Into<KeyOrValue<f64>>) -> SizedBox<T>
Wrap this widget in a SizedBox
with an explicit height.
Examples found in repository?
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 96 97 98 99
fn build_root_widget() -> impl Widget<AppState> {
let range = Flex::row()
.with_child(Label::dynamic(|value: &(f64, f64), _| {
format!("Value Range: {value:?}")
}))
.with_default_spacer()
.with_child(
RangeSlider::new()
.with_range(0.0, 20.0)
.with_step(1.0)
.track_color(KeyOrValue::Concrete(Color::RED))
.fix_width(250.0),
)
.lens(AppState::range);
let value = Flex::row()
.with_child(Label::dynamic(|value: &AppState, _| {
format!("Value: {:?}", value.value)
}))
.with_default_spacer()
.with_child(ViewSwitcher::new(
|data: &AppState, _| data.range,
|range, _, _| {
Slider::new()
.with_range(range.0, range.1)
.track_color(KeyOrValue::Concrete(Color::RED))
.knob_style(KnobStyle::Wedge)
.axis(Axis::Vertical)
.with_step(0.25)
.annotated(1.0, 0.25)
.fix_height(250.0)
.lens(AppState::value)
.boxed()
},
));
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(range)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(value)
.cross_axis_alignment(CrossAxisAlignment::End)
.align_vertical(UnitPoint::RIGHT)
.padding(20.0)
}
More examples
24 25 26 27 28 29 30 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
fn build_app() -> impl Widget<u32> {
// Usually we put all the widgets in one big tree using builder-style
// methods. Sometimes we split them up in declarations to increase
// readability. In this case we also have some recurring elements,
// we add those in a loop later on.
let mut col = Flex::column().with_child(
// The `Flex`'s first child is another Flex! In this case it is
// a row.
Flex::row()
// The row has its own children.
.with_child(
Label::new("One")
.fix_width(60.0)
.background(Color::rgb8(0x77, 0x77, 0))
.border(Color::WHITE, 3.0)
.center(),
)
// Spacing element that will fill all available space in
// between label and a button. Notice that weight is non-zero.
// We could have achieved a similar result with expanding the
// width and setting the main-axis-allignment to SpaceBetween.
.with_flex_spacer(1.0)
.with_child(Button::new("Two").padding(20.))
// After we added all the children, we can set some more
// values using builder-style methods. Since these methods
// dont return the original `Flex` but a SizedBox and Container
// respectively, we have to put these at the end.
.fix_height(100.0)
//turquoise
.background(Color::rgb8(0, 0x77, 0x88)),
);
for i in 0..5 {
// Give a larger weight to one of the buttons for it to
// occupy more space.
let weight = if i == 2 { 3.0 } else { 1.0 };
// call `expand_height` to force the buttons to use all their provided flex
col.add_flex_child(Button::new(format!("Button #{i}")).expand_height(), weight);
}
// aspect ratio box
let aspect_ratio_label = Label::new("This is an aspect-ratio box. Notice how the text will overflow if the box becomes too small.")
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.center();
let aspect_ratio_box = AspectRatioBox::new(aspect_ratio_label, 4.0)
.border(Color::BLACK, 1.0)
.background(Color::WHITE);
col.add_flex_child(aspect_ratio_box.center(), 1.0);
// This method asks Druid to draw colored rectangles around our widgets,
// so we can visually inspect their layout rectangles.
col.debug_paint_layout()
}
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 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
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn fix_size(
self,
width: impl Into<KeyOrValue<f64>>,
height: impl Into<KeyOrValue<f64>>
) -> SizedBox<T>
fn fix_size( self, width: impl Into<KeyOrValue<f64>>, height: impl Into<KeyOrValue<f64>> ) -> SizedBox<T>
Wrap this widget in an SizedBox
with an explicit width and height
Examples found in repository?
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
fn interactive_area() -> impl Widget<AppState> {
let text_box = TextBox::multiline()
.with_text_color(Color::rgb8(0xf0, 0xf0, 0xea))
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.lens(AppState::text_input)
.controller(EventLogger {
filter: |event| matches!(event, Event::KeyDown(_) | Event::KeyUp(_)),
});
let mouse_box = SizedBox::empty()
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.background(CURSOR_BACKGROUND_COLOR)
.rounded(5.0)
.border(INTERACTIVE_AREA_BORDER, 1.0)
.controller(EventLogger {
filter: |event| {
matches!(
event,
Event::MouseDown(_) | Event::MouseUp(_) | Event::Wheel(_)
)
},
});
Flex::row()
.with_flex_spacer(1.0)
.with_child(text_box)
.with_flex_spacer(1.0)
.with_child(mouse_box)
.with_flex_spacer(1.0)
.padding(10.0)
}
More examples
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 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
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn expand(self) -> SizedBox<T>
fn expand(self) -> SizedBox<T>
Wrap this widget in a SizedBox
with an infinite width and height.
Only call this method if you want your widget to occupy all available
space. If you only care about expanding in one of width or height, use
expand_width
or expand_height
instead.
Examples found in repository?
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
More examples
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))
}
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)
}
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 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
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn expand_width(self) -> SizedBox<T>
fn expand_width(self) -> SizedBox<T>
Wrap this widget in a SizedBox
with an infinite width.
This will force the child to use all available space on the x-axis.
Examples found in repository?
103 104 105 106 107 108 109 110 111 112 113 114 115
fn group<T: Data, W: Widget<T> + 'static>(text: &str, w: W) -> impl Widget<T> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(
Label::new(text)
.background(theme::PLACEHOLDER_COLOR)
.expand_width(),
)
.with_default_spacer()
.with_child(w)
.with_default_spacer()
.border(Color::WHITE, 0.5)
}
More examples
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let blurb = Label::new(EXPLAINER)
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(blurb)
.with_spacer(24.0)
.with_child(
TextBox::new()
.with_placeholder("Single")
.lens(AppState::single),
)
.with_default_spacer()
.with_flex_child(
TextBox::multiline()
.with_placeholder("Multi")
.lens(AppState::multi)
.expand_width(),
1.0,
)
.padding(8.0)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.controller(LabelController)
.background(Color::WHITE)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE))
.background(Color::grey8(222)),
)
.vertical();
let line_break_chooser = Flex::column()
.with_child(Label::new("Line break mode"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Clip", LineBreaking::Clip),
("Wrap", LineBreaking::WordWrap),
("Overflow", LineBreaking::Overflow),
]))
.lens(AppState::line_break_mode);
let alignment_picker = Flex::column()
.with_child(Label::new("Justification"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Start", TextAlignment::Start),
("End", TextAlignment::End),
("Center", TextAlignment::Center),
("Justified", TextAlignment::Justified),
]))
.lens(AppState::alignment);
let controls = Flex::row()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(alignment_picker)
.with_spacer(SPACER_SIZE)
.with_child(line_break_chooser)
.padding(SPACER_SIZE);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(controls)
.with_flex_child(label, 1.0)
}
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
fn make_widget() -> impl Widget<AppData> {
Flex::column()
.with_flex_child(
GameOfLifeWidget {
timer_id: TimerToken::INVALID,
cell_size: Size {
width: 0.0,
height: 0.0,
},
last_update: Instant::now(),
},
1.0,
)
.with_child(
Flex::column()
.with_child(
// a row with two buttons
Flex::row()
.with_flex_child(
// pause / resume button
Button::new(|data: &bool, _: &Env| match data {
true => "Resume",
false => "Pause",
})
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
ctx.request_layout();
})
.lens(AppData::paused)
.padding((5., 5.)),
1.0,
)
.with_flex_child(
// clear button
Button::new("Clear")
.on_click(|ctx, data: &mut Grid, _: &Env| {
data.clear();
ctx.request_paint();
})
.lens(AppData::grid)
.padding((5., 5.)),
1.0,
)
.padding(8.0),
)
.with_child(
Flex::row()
.with_child(
Label::new(|data: &AppData, _env: &_| {
format!("{:.2}updates/s", data.updates_per_second)
})
.padding(3.0),
)
.with_flex_child(
Slider::new()
.with_range(0.2, 20.0)
.expand_width()
.lens(AppData::updates_per_second),
1.,
)
.padding(8.0),
)
.background(BACKGROUND),
)
}
sourcefn expand_height(self) -> SizedBox<T>
fn expand_height(self) -> SizedBox<T>
Wrap this widget in a SizedBox
with an infinite width.
This will force the child to use all available space on the y-axis.
Examples found in repository?
24 25 26 27 28 29 30 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
fn build_app() -> impl Widget<u32> {
// Usually we put all the widgets in one big tree using builder-style
// methods. Sometimes we split them up in declarations to increase
// readability. In this case we also have some recurring elements,
// we add those in a loop later on.
let mut col = Flex::column().with_child(
// The `Flex`'s first child is another Flex! In this case it is
// a row.
Flex::row()
// The row has its own children.
.with_child(
Label::new("One")
.fix_width(60.0)
.background(Color::rgb8(0x77, 0x77, 0))
.border(Color::WHITE, 3.0)
.center(),
)
// Spacing element that will fill all available space in
// between label and a button. Notice that weight is non-zero.
// We could have achieved a similar result with expanding the
// width and setting the main-axis-allignment to SpaceBetween.
.with_flex_spacer(1.0)
.with_child(Button::new("Two").padding(20.))
// After we added all the children, we can set some more
// values using builder-style methods. Since these methods
// dont return the original `Flex` but a SizedBox and Container
// respectively, we have to put these at the end.
.fix_height(100.0)
//turquoise
.background(Color::rgb8(0, 0x77, 0x88)),
);
for i in 0..5 {
// Give a larger weight to one of the buttons for it to
// occupy more space.
let weight = if i == 2 { 3.0 } else { 1.0 };
// call `expand_height` to force the buttons to use all their provided flex
col.add_flex_child(Button::new(format!("Button #{i}")).expand_height(), weight);
}
// aspect ratio box
let aspect_ratio_label = Label::new("This is an aspect-ratio box. Notice how the text will overflow if the box becomes too small.")
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.center();
let aspect_ratio_box = AspectRatioBox::new(aspect_ratio_label, 4.0)
.border(Color::BLACK, 1.0)
.background(Color::WHITE);
col.add_flex_child(aspect_ratio_box.center(), 1.0);
// This method asks Druid to draw colored rectangles around our widgets,
// so we can visually inspect their layout rectangles.
col.debug_paint_layout()
}
More examples
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()
}
sourcefn background(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T>
fn background(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T>
Wrap this widget in a Container
with the provided background brush
.
See Container::background
for more information.
Examples found in repository?
103 104 105 106 107 108 109 110 111 112 113 114 115
fn group<T: Data, W: Widget<T> + 'static>(text: &str, w: W) -> impl Widget<T> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(
Label::new(text)
.background(theme::PLACEHOLDER_COLOR)
.expand_width(),
)
.with_default_spacer()
.with_child(w)
.with_default_spacer()
.border(Color::WHITE, 0.5)
}
More examples
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
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))
}
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
fn interactive_area() -> impl Widget<AppState> {
let text_box = TextBox::multiline()
.with_text_color(Color::rgb8(0xf0, 0xf0, 0xea))
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.lens(AppState::text_input)
.controller(EventLogger {
filter: |event| matches!(event, Event::KeyDown(_) | Event::KeyUp(_)),
});
let mouse_box = SizedBox::empty()
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.background(CURSOR_BACKGROUND_COLOR)
.rounded(5.0)
.border(INTERACTIVE_AREA_BORDER, 1.0)
.controller(EventLogger {
filter: |event| {
matches!(
event,
Event::MouseDown(_) | Event::MouseUp(_) | Event::Wheel(_)
)
},
});
Flex::row()
.with_flex_spacer(1.0)
.with_child(text_box)
.with_flex_spacer(1.0)
.with_child(mouse_box)
.with_flex_spacer(1.0)
.padding(10.0)
}
/// The bottom part of the application, a list of received events.
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.controller(LabelController)
.background(Color::WHITE)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE))
.background(Color::grey8(222)),
)
.vertical();
let line_break_chooser = Flex::column()
.with_child(Label::new("Line break mode"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Clip", LineBreaking::Clip),
("Wrap", LineBreaking::WordWrap),
("Overflow", LineBreaking::Overflow),
]))
.lens(AppState::line_break_mode);
let alignment_picker = Flex::column()
.with_child(Label::new("Justification"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Start", TextAlignment::Start),
("End", TextAlignment::End),
("Center", TextAlignment::Center),
("Justified", TextAlignment::Justified),
]))
.lens(AppState::alignment);
let controls = Flex::row()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(alignment_picker)
.with_spacer(SPACER_SIZE)
.with_child(line_break_chooser)
.padding(SPACER_SIZE);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(controls)
.with_flex_child(label, 1.0)
}
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
pub fn new() -> Self {
let info_label = Label::new(INFO_TEXT)
.with_line_break_mode(LineBreaking::WordWrap)
.padding(20.0)
.background(Color::rgba(0.2, 0.2, 0.2, 1.0));
let toggle_input_region = Button::new("Toggle Input Region")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting input region toggle to: {}", *data);
ctx.request_layout();
})
.lens(AppState::limit_input_region);
let toggle_titlebar = Button::new("Toggle TitleBar")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting titlebar visibility to: {}", *data);
ctx.window().show_titlebar(*data);
ctx.request_layout();
})
.lens(AppState::show_titlebar);
let toggle_always_on_top = Button::new("Toggle Always On Top")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting always on top to: {}", *data);
ctx.window().set_always_on_top(*data);
})
.lens(AppState::always_on_top);
let controls_flex = Flex::row()
.with_child(toggle_input_region)
.with_child(toggle_titlebar)
.with_child(toggle_always_on_top);
Self {
info_label: WidgetPod::new(info_label),
controls: WidgetPod::new(controls_flex),
}
}
sourcefn foreground(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T>
fn foreground(self, brush: impl Into<BackgroundBrush<T>>) -> Container<T>
Wrap this widget in a Container
with the provided foreground brush
.
See Container::foreground
for more information.
sourcefn border(
self,
color: impl Into<KeyOrValue<Color>>,
width: impl Into<KeyOrValue<f64>>
) -> Container<T>
fn border( self, color: impl Into<KeyOrValue<Color>>, width: impl Into<KeyOrValue<f64>> ) -> Container<T>
Wrap this widget in a Container
with the given border.
Arguments can be either concrete values, or a Key
of the respective
type.
Examples found in repository?
103 104 105 106 107 108 109 110 111 112 113 114 115
fn group<T: Data, W: Widget<T> + 'static>(text: &str, w: W) -> impl Widget<T> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(
Label::new(text)
.background(theme::PLACEHOLDER_COLOR)
.expand_width(),
)
.with_default_spacer()
.with_child(w)
.with_default_spacer()
.border(Color::WHITE, 0.5)
}
More examples
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
fn build_root_widget() -> impl Widget<AppState> {
let blurb = Label::new(EXPLAINER)
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(blurb)
.with_spacer(24.0)
.with_child(
TextBox::new()
.with_placeholder("Single")
.lens(AppState::single),
)
.with_default_spacer()
.with_flex_child(
TextBox::multiline()
.with_placeholder("Multi")
.lens(AppState::multi)
.expand_width(),
1.0,
)
.padding(8.0)
}
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,
)
}
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
fn make_control_row() -> impl Widget<AppState> {
Flex::row()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Type:"))
.with_default_spacer()
.with_child(RadioGroup::column(FLEX_TYPE_OPTIONS.to_vec()).lens(Params::axis)),
)
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("CrossAxis:"))
.with_default_spacer()
.with_child(
RadioGroup::column(CROSS_AXIS_ALIGNMENT_OPTIONS.to_vec())
.lens(Params::cross_alignment),
),
)
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("MainAxis:"))
.with_default_spacer()
.with_child(
RadioGroup::column(MAIN_AXIS_ALIGNMENT_OPTIONS.to_vec())
.lens(Params::main_alignment),
),
)
.with_default_spacer()
.with_child(make_spacer_select())
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Misc:"))
.with_default_spacer()
.with_child(Checkbox::new("Debug layout").lens(Params::debug_layout))
.with_default_spacer()
.with_child(Checkbox::new("Fill main axis").lens(Params::fill_major_axis))
.with_default_spacer()
.with_child(Checkbox::new("Fix minor axis size").lens(Params::fix_minor_axis))
.with_default_spacer()
.with_child(Checkbox::new("Fix major axis size").lens(Params::fix_major_axis)),
)
.padding(10.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0)
.lens(AppState::params)
}
fn make_spacer_select() -> impl Widget<Params> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Insert Spacers:"))
.with_default_spacer()
.with_child(RadioGroup::column(SPACER_OPTIONS.to_vec()).lens(Params::spacers))
.with_default_spacer()
.with_child(
Flex::row()
.with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(Params::spacer_size)
.fix_width(60.0),
)
.with_spacer(druid::theme::WIDGET_CONTROL_COMPONENT_PADDING)
.with_child(
Stepper::new()
.with_range(2.0, 50.0)
.with_step(2.0)
.lens(Params::spacer_size),
),
)
}
fn space_if_needed<T: Data>(flex: &mut Flex<T>, params: &Params) {
match params.spacers {
Spacers::None => (),
Spacers::Default => flex.add_default_spacer(),
Spacers::Fixed => flex.add_spacer(params.spacer_size),
Spacers::Flex => flex.add_flex_spacer(1.0),
}
}
fn build_widget(state: &Params) -> Box<dyn Widget<AppState>> {
let mut flex = match state.axis {
FlexType::Column => Flex::column(),
FlexType::Row => Flex::row(),
}
.cross_axis_alignment(state.cross_alignment)
.main_axis_alignment(state.main_alignment)
.must_fill_main_axis(state.fill_major_axis);
flex.add_child(
TextBox::new()
.with_placeholder("Sample text")
.lens(DemoState::input_text),
);
space_if_needed(&mut flex, state);
flex.add_child(
Button::new("Clear").on_click(|_ctx, data: &mut DemoState, _env| {
data.input_text.clear();
data.enabled = false;
data.volume = 0.0;
}),
);
space_if_needed(&mut flex, state);
flex.add_child(
Label::new(|data: &DemoState, _: &Env| data.input_text.clone()).with_text_size(32.0),
);
space_if_needed(&mut flex, state);
flex.add_child(Checkbox::new("Demo").lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Switch::new().lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Slider::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(ProgressBar::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(
Stepper::new()
.with_range(0.0, 1.0)
.with_step(0.1)
.with_wraparound(true)
.lens(DemoState::volume),
);
let mut flex = SizedBox::new(flex);
if state.fix_minor_axis {
match state.axis {
FlexType::Row => flex = flex.height(200.),
FlexType::Column => flex = flex.width(200.),
}
}
if state.fix_major_axis {
match state.axis {
FlexType::Row => flex = flex.width(600.),
FlexType::Column => flex = flex.height(300.),
}
}
let flex = flex
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0)
.lens(AppState::demo_state);
if state.debug_layout {
flex.debug_paint_layout().boxed()
} else {
flex.boxed()
}
}
24 25 26 27 28 29 30 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
fn build_app() -> impl Widget<u32> {
// Usually we put all the widgets in one big tree using builder-style
// methods. Sometimes we split them up in declarations to increase
// readability. In this case we also have some recurring elements,
// we add those in a loop later on.
let mut col = Flex::column().with_child(
// The `Flex`'s first child is another Flex! In this case it is
// a row.
Flex::row()
// The row has its own children.
.with_child(
Label::new("One")
.fix_width(60.0)
.background(Color::rgb8(0x77, 0x77, 0))
.border(Color::WHITE, 3.0)
.center(),
)
// Spacing element that will fill all available space in
// between label and a button. Notice that weight is non-zero.
// We could have achieved a similar result with expanding the
// width and setting the main-axis-allignment to SpaceBetween.
.with_flex_spacer(1.0)
.with_child(Button::new("Two").padding(20.))
// After we added all the children, we can set some more
// values using builder-style methods. Since these methods
// dont return the original `Flex` but a SizedBox and Container
// respectively, we have to put these at the end.
.fix_height(100.0)
//turquoise
.background(Color::rgb8(0, 0x77, 0x88)),
);
for i in 0..5 {
// Give a larger weight to one of the buttons for it to
// occupy more space.
let weight = if i == 2 { 3.0 } else { 1.0 };
// call `expand_height` to force the buttons to use all their provided flex
col.add_flex_child(Button::new(format!("Button #{i}")).expand_height(), weight);
}
// aspect ratio box
let aspect_ratio_label = Label::new("This is an aspect-ratio box. Notice how the text will overflow if the box becomes too small.")
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.center();
let aspect_ratio_box = AspectRatioBox::new(aspect_ratio_label, 4.0)
.border(Color::BLACK, 1.0)
.background(Color::WHITE);
col.add_flex_child(aspect_ratio_box.center(), 1.0);
// This method asks Druid to draw colored rectangles around our widgets,
// so we can visually inspect their layout rectangles.
col.debug_paint_layout()
}
sourcefn env_scope(self, f: impl Fn(&mut Env, &T) + 'static) -> EnvScope<T, Self>
fn env_scope(self, f: impl Fn(&mut Env, &T) + 'static) -> EnvScope<T, Self>
Examples found in repository?
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()
}
sourcefn controller<C: Controller<T, Self>>(
self,
controller: C
) -> ControllerHost<Self, C>
fn controller<C: Controller<T, Self>>( self, controller: C ) -> ControllerHost<Self, C>
Wrap this widget with the provided Controller
.
Examples found in repository?
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
More examples
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
fn interactive_area() -> impl Widget<AppState> {
let text_box = TextBox::multiline()
.with_text_color(Color::rgb8(0xf0, 0xf0, 0xea))
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.lens(AppState::text_input)
.controller(EventLogger {
filter: |event| matches!(event, Event::KeyDown(_) | Event::KeyUp(_)),
});
let mouse_box = SizedBox::empty()
.fix_size(INTERACTIVE_AREA_DIM, INTERACTIVE_AREA_DIM)
.background(CURSOR_BACKGROUND_COLOR)
.rounded(5.0)
.border(INTERACTIVE_AREA_BORDER, 1.0)
.controller(EventLogger {
filter: |event| {
matches!(
event,
Event::MouseDown(_) | Event::MouseUp(_) | Event::Wheel(_)
)
},
});
Flex::row()
.with_flex_spacer(1.0)
.with_child(text_box)
.with_flex_spacer(1.0)
.with_child(mouse_box)
.with_flex_spacer(1.0)
.padding(10.0)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.controller(LabelController)
.background(Color::WHITE)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE))
.background(Color::grey8(222)),
)
.vertical();
let line_break_chooser = Flex::column()
.with_child(Label::new("Line break mode"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Clip", LineBreaking::Clip),
("Wrap", LineBreaking::WordWrap),
("Overflow", LineBreaking::Overflow),
]))
.lens(AppState::line_break_mode);
let alignment_picker = Flex::column()
.with_child(Label::new("Justification"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Start", TextAlignment::Start),
("End", TextAlignment::End),
("Center", TextAlignment::Center),
("Justified", TextAlignment::Justified),
]))
.lens(AppState::alignment);
let controls = Flex::row()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(alignment_picker)
.with_spacer(SPACER_SIZE)
.with_child(line_break_chooser)
.padding(SPACER_SIZE);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(controls)
.with_flex_child(label, 1.0)
}
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
fn make_ui() -> impl Widget<OurData> {
// We can also generate these dynamically whenever we need it.
let id_two = WidgetId::next();
// We have a column with 2 labels and 2 buttons.
// Each of the 2 labels only has access to its own counter and is given a `WidgetId`.
// Both labels have a controller, this handles commands send to children.
// The 2 buttons send a command when clicked. Both send the exact same command.
// The key diference is that they both give a different `WidgetId` as target.
// This means that only the corresponding controller gets the command, and increments their counter.
Flex::column()
.with_child(
Label::dynamic(|data, _| format!("One: {data}"))
.controller(LabelControler)
.with_id(ID_ONE)
.lens(OurData::counter_one)
.padding(2.0),
)
.with_child(
Label::dynamic(|data, _| format!("Two: {data}"))
.controller(LabelControler)
.with_id(id_two)
.lens(OurData::counter_two)
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment one")
.on_click(|ctx, _data, _env| ctx.submit_command(INCREMENT.to(ID_ONE)))
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment two")
.on_click(move |ctx, _data, _env| ctx.submit_command(INCREMENT.to(id_two)))
.padding(2.0),
)
.padding(10.0)
}
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)
}
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
fn build_root_widget() -> impl Widget<HelloState> {
let label = EnvScope::new(
|env, _t| env.set(theme::TEXT_COLOR, env.get(theme::PRIMARY_LIGHT)),
ControllerHost::new(
Label::new(|data: &HelloState, _env: &Env| {
format!("Hello {}! {} ", data.name, data.sub.my_stuff)
}),
TooltipController::new("Tips! Are good"),
),
);
// a textbox that modifies `name`.
let textbox = TextBox::new()
.with_placeholder("Who are we greeting?")
.fix_width(TEXT_BOX_WIDTH)
.lens(HelloState::sub.then(SubState::my_stuff));
let button = Button::new("Make sub window")
.on_click(|ctx, data: &mut SubState, env| {
let tb = TextBox::new().lens(SubState::my_stuff);
let drag_thing = Label::new("Drag me").controller(DragWindowController::new());
let col = Flex::column().with_child(drag_thing).with_child(tb);
ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size(Size::new(100., 100.))
.set_level(WindowLevel::AppWindow),
col,
data.clone(),
env.clone(),
);
})
.center()
.lens(HelloState::sub);
let check_box =
ControllerHost::new(Checkbox::new("Closeable?"), CancelClose).lens(HelloState::closeable);
// arrange the two widgets vertically, with some padding
let layout = Flex::column()
.with_child(label)
.with_flex_child(ScreenThing.lens(Unit::default()).padding(5.), 1.)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(textbox)
.with_child(button)
.with_child(check_box);
// center the two widgets in the available space
Align::centered(layout)
}
sourcefn on_added(
self,
f: impl Fn(&mut Self, &mut LifeCycleCtx<'_, '_>, &T, &Env) + 'static
) -> ControllerHost<Self, Added<T, Self>>
fn on_added( self, f: impl Fn(&mut Self, &mut LifeCycleCtx<'_, '_>, &T, &Env) + 'static ) -> ControllerHost<Self, Added<T, Self>>
Provide a closure that will be called when this widget is added to the widget tree.
You can use this to perform any initial setup.
This is equivalent to handling the LifeCycle::WidgetAdded
event in a
custom Controller
.
sourcefn on_click(
self,
f: impl Fn(&mut EventCtx<'_, '_>, &mut T, &Env) + 'static
) -> ControllerHost<Self, Click<T>>
fn on_click( self, f: impl Fn(&mut EventCtx<'_, '_>, &mut T, &Env) + 'static ) -> ControllerHost<Self, Click<T>>
Control the events of this widget with a Click
widget. The closure
provided will be called when the widget is clicked with the left mouse
button.
The child widget will also be updated on LifeCycle::HotChanged
and
mouse down, which can be useful for painting based on ctx.is_active()
and ctx.is_hot()
.
Examples found in repository?
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))
}
More examples
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()
}
sourcefn debug_paint_layout(self) -> EnvScope<T, Self>
fn debug_paint_layout(self) -> EnvScope<T, Self>
Draw the layout
Rect
s of this widget and its children.
Examples found in repository?
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn ui_builder() -> impl Widget<u32> {
let tiger_svg = match include_str!("./assets/tiger.svg").parse::<SvgData>() {
Ok(svg) => svg,
Err(err) => {
error!("{}", err);
error!("Using an empty SVG instead.");
SvgData::default()
}
};
let mut col = Flex::column();
col.add_flex_child(Svg::new(tiger_svg.clone()).fix_width(60.0).center(), 1.0);
col.add_flex_child(Svg::new(tiger_svg.clone()), 1.0);
col.add_flex_child(Svg::new(tiger_svg), 1.0);
col.debug_paint_layout()
}
More examples
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
fn build_widget(state: &Params) -> Box<dyn Widget<AppState>> {
let mut flex = match state.axis {
FlexType::Column => Flex::column(),
FlexType::Row => Flex::row(),
}
.cross_axis_alignment(state.cross_alignment)
.main_axis_alignment(state.main_alignment)
.must_fill_main_axis(state.fill_major_axis);
flex.add_child(
TextBox::new()
.with_placeholder("Sample text")
.lens(DemoState::input_text),
);
space_if_needed(&mut flex, state);
flex.add_child(
Button::new("Clear").on_click(|_ctx, data: &mut DemoState, _env| {
data.input_text.clear();
data.enabled = false;
data.volume = 0.0;
}),
);
space_if_needed(&mut flex, state);
flex.add_child(
Label::new(|data: &DemoState, _: &Env| data.input_text.clone()).with_text_size(32.0),
);
space_if_needed(&mut flex, state);
flex.add_child(Checkbox::new("Demo").lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Switch::new().lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Slider::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(ProgressBar::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(
Stepper::new()
.with_range(0.0, 1.0)
.with_step(0.1)
.with_wraparound(true)
.lens(DemoState::volume),
);
let mut flex = SizedBox::new(flex);
if state.fix_minor_axis {
match state.axis {
FlexType::Row => flex = flex.height(200.),
FlexType::Column => flex = flex.width(200.),
}
}
if state.fix_major_axis {
match state.axis {
FlexType::Row => flex = flex.width(600.),
FlexType::Column => flex = flex.height(300.),
}
}
let flex = flex
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0)
.lens(AppState::demo_state);
if state.debug_layout {
flex.debug_paint_layout().boxed()
} else {
flex.boxed()
}
}
24 25 26 27 28 29 30 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
fn build_app() -> impl Widget<u32> {
// Usually we put all the widgets in one big tree using builder-style
// methods. Sometimes we split them up in declarations to increase
// readability. In this case we also have some recurring elements,
// we add those in a loop later on.
let mut col = Flex::column().with_child(
// The `Flex`'s first child is another Flex! In this case it is
// a row.
Flex::row()
// The row has its own children.
.with_child(
Label::new("One")
.fix_width(60.0)
.background(Color::rgb8(0x77, 0x77, 0))
.border(Color::WHITE, 3.0)
.center(),
)
// Spacing element that will fill all available space in
// between label and a button. Notice that weight is non-zero.
// We could have achieved a similar result with expanding the
// width and setting the main-axis-allignment to SpaceBetween.
.with_flex_spacer(1.0)
.with_child(Button::new("Two").padding(20.))
// After we added all the children, we can set some more
// values using builder-style methods. Since these methods
// dont return the original `Flex` but a SizedBox and Container
// respectively, we have to put these at the end.
.fix_height(100.0)
//turquoise
.background(Color::rgb8(0, 0x77, 0x88)),
);
for i in 0..5 {
// Give a larger weight to one of the buttons for it to
// occupy more space.
let weight = if i == 2 { 3.0 } else { 1.0 };
// call `expand_height` to force the buttons to use all their provided flex
col.add_flex_child(Button::new(format!("Button #{i}")).expand_height(), weight);
}
// aspect ratio box
let aspect_ratio_label = Label::new("This is an aspect-ratio box. Notice how the text will overflow if the box becomes too small.")
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.center();
let aspect_ratio_box = AspectRatioBox::new(aspect_ratio_label, 4.0)
.border(Color::BLACK, 1.0)
.background(Color::WHITE);
col.add_flex_child(aspect_ratio_box.center(), 1.0);
// This method asks Druid to draw colored rectangles around our widgets,
// so we can visually inspect their layout rectangles.
col.debug_paint_layout()
}
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 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
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn debug_widget_id(self) -> EnvScope<T, Self>
fn debug_widget_id(self) -> EnvScope<T, Self>
Display the WidgetId
s for this widget and its children, when hot.
When this is true
, widgets that are hot
(are under the mouse cursor)
will display their ids in their bottom right corner.
These ids may overlap; in this case the id of a child will obscure the id of its parent.
sourcefn debug_invalidation(self) -> DebugInvalidation<T, Self>
fn debug_invalidation(self) -> DebugInvalidation<T, Self>
Draw a color-changing rectangle over this widget, allowing you to see the invalidation regions.
sourcefn debug_widget(self) -> EnvScope<T, Self>
fn debug_widget(self) -> EnvScope<T, Self>
Set the DEBUG_WIDGET
env variable for this widget (and its descendants).
This does nothing by default, but you can use this variable while debugging to only print messages from particular instances of a widget.
sourcefn lens<S: Data, L: Lens<S, T>>(self, lens: L) -> LensWrap<S, T, L, Self>
fn lens<S: Data, L: Lens<S, T>>(self, lens: L) -> LensWrap<S, T, L, Self>
Examples found in repository?
More examples
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
fn ui_builder() -> impl Widget<AppState> {
// Our UI consists of a column with a button and an `Either` widget
let button = Checkbox::new("Toggle slider")
.lens(AppState::which)
.padding(5.0);
// The `Either` widget has two children, only one of which is visible at a time.
// To determine which child is visible, you pass it a closure that takes the
// `Data` and the `Env` and returns a bool; if it returns `true`, the first
// widget will be visible, and if `false`, the second.
let either = Either::new(
|data, _env| data.which,
Slider::new().lens(AppState::value).padding(5.0),
Label::new("Click to reveal slider").padding(5.0),
);
Flex::column().with_child(button).with_child(either)
}
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
fn build_root_widget() -> impl Widget<AppState> {
let blurb = Label::new(EXPLAINER)
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(blurb)
.with_spacer(24.0)
.with_child(
TextBox::new()
.with_placeholder("Single")
.lens(AppState::single),
)
.with_default_spacer()
.with_flex_child(
TextBox::multiline()
.with_placeholder("Multi")
.lens(AppState::multi)
.expand_width(),
1.0,
)
.padding(8.0)
}
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
fn build_root_widget() -> impl Widget<HelloState> {
// a label that will determine its text based on the current app data.
let label = Label::new(|data: &HelloState, _env: &Env| {
if data.name.is_empty() {
"Hello anybody!?".to_string()
} else {
format!("Hello {}!", data.name)
}
})
.with_text_size(32.0);
// a textbox that modifies `name`.
let textbox = TextBox::new()
.with_placeholder("Who are we greeting?")
.with_text_size(18.0)
.fix_width(TEXT_BOX_WIDTH)
.lens(HelloState::name);
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(label)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(textbox)
.align_vertical(UnitPoint::CENTER)
}
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
fn ui_builder() -> impl Widget<MyComplexState> {
// `TextBox` is of type `Widget<String>`
// via `.lens` we get it to be of type `Widget<MyComplexState>`
let searchbar = TextBox::new().lens(MyComplexState::term_lens);
// `Slider` is of type `Widget<f64>`
// via `.lens` we get it to be of type `Widget<MyComplexState>`
let slider = Slider::new().lens(MyComplexState::scale);
let label = Label::new(|d: &MyComplexState, _: &Env| format!("{}: {:.2}", d.term, d.scale));
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(label)
.with_default_spacer()
.with_child(
Flex::row()
.cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(searchbar)
.with_default_spacer()
.with_child(slider),
)
.center()
}
- examples/event_viewer.rs
- examples/slider.rs
- examples/text.rs
- examples/identity.rs
- examples/input_region.rs
- examples/transparency.rs
- examples/calc.rs
- examples/sub_window.rs
- examples/tabs.rs
- examples/flex.rs
- examples/view_switcher.rs
- examples/game_of_life.rs
- examples/disabled.rs
- examples/styled_text.rs
- examples/list.rs
sourcefn with_id(self, id: WidgetId) -> IdentityWrapper<Self>
fn with_id(self, id: WidgetId) -> IdentityWrapper<Self>
Assign the widget a specific WidgetId
.
You must ensure that a given WidgetId
is only ever used for
a single widget at a time.
An id may be reused over time; for instance if you replace one widget with another, you may reuse the first widget’s id.
Examples found in repository?
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
fn make_ui() -> impl Widget<OurData> {
// We can also generate these dynamically whenever we need it.
let id_two = WidgetId::next();
// We have a column with 2 labels and 2 buttons.
// Each of the 2 labels only has access to its own counter and is given a `WidgetId`.
// Both labels have a controller, this handles commands send to children.
// The 2 buttons send a command when clicked. Both send the exact same command.
// The key diference is that they both give a different `WidgetId` as target.
// This means that only the corresponding controller gets the command, and increments their counter.
Flex::column()
.with_child(
Label::dynamic(|data, _| format!("One: {data}"))
.controller(LabelControler)
.with_id(ID_ONE)
.lens(OurData::counter_one)
.padding(2.0),
)
.with_child(
Label::dynamic(|data, _| format!("Two: {data}"))
.controller(LabelControler)
.with_id(id_two)
.lens(OurData::counter_two)
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment one")
.on_click(|ctx, _data, _env| ctx.submit_command(INCREMENT.to(ID_ONE)))
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment two")
.on_click(move |ctx, _data, _env| ctx.submit_command(INCREMENT.to(id_two)))
.padding(2.0),
)
.padding(10.0)
}
sourcefn boxed(self) -> Box<dyn Widget<T>>
fn boxed(self) -> Box<dyn Widget<T>>
Wrap this widget in a Box
.
Examples found in repository?
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
fn new() -> Rebuilder {
Rebuilder {
inner: SizedBox::empty().boxed(),
}
}
fn rebuild_inner(&mut self, data: &AppState) {
self.inner = build_widget(&data.params);
}
}
impl Widget<AppState> for Rebuilder {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, env: &Env) {
self.inner.event(ctx, event, data, env)
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &AppState, env: &Env) {
if let LifeCycle::WidgetAdded = event {
self.rebuild_inner(data);
}
self.inner.lifecycle(ctx, event, data, env)
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppState, data: &AppState, env: &Env) {
if !old_data.params.same(&data.params) {
self.rebuild_inner(data);
ctx.children_changed();
} else {
self.inner.update(ctx, old_data, data, env);
}
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &AppState,
env: &Env,
) -> Size {
self.inner.layout(ctx, bc, data, env)
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &AppState, env: &Env) {
self.inner.paint(ctx, data, env)
}
fn id(&self) -> Option<WidgetId> {
self.inner.id()
}
}
fn make_control_row() -> impl Widget<AppState> {
Flex::row()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Type:"))
.with_default_spacer()
.with_child(RadioGroup::column(FLEX_TYPE_OPTIONS.to_vec()).lens(Params::axis)),
)
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("CrossAxis:"))
.with_default_spacer()
.with_child(
RadioGroup::column(CROSS_AXIS_ALIGNMENT_OPTIONS.to_vec())
.lens(Params::cross_alignment),
),
)
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("MainAxis:"))
.with_default_spacer()
.with_child(
RadioGroup::column(MAIN_AXIS_ALIGNMENT_OPTIONS.to_vec())
.lens(Params::main_alignment),
),
)
.with_default_spacer()
.with_child(make_spacer_select())
.with_default_spacer()
.with_child(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Misc:"))
.with_default_spacer()
.with_child(Checkbox::new("Debug layout").lens(Params::debug_layout))
.with_default_spacer()
.with_child(Checkbox::new("Fill main axis").lens(Params::fill_major_axis))
.with_default_spacer()
.with_child(Checkbox::new("Fix minor axis size").lens(Params::fix_minor_axis))
.with_default_spacer()
.with_child(Checkbox::new("Fix major axis size").lens(Params::fix_major_axis)),
)
.padding(10.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0)
.lens(AppState::params)
}
fn make_spacer_select() -> impl Widget<Params> {
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Insert Spacers:"))
.with_default_spacer()
.with_child(RadioGroup::column(SPACER_OPTIONS.to_vec()).lens(Params::spacers))
.with_default_spacer()
.with_child(
Flex::row()
.with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(Params::spacer_size)
.fix_width(60.0),
)
.with_spacer(druid::theme::WIDGET_CONTROL_COMPONENT_PADDING)
.with_child(
Stepper::new()
.with_range(2.0, 50.0)
.with_step(2.0)
.lens(Params::spacer_size),
),
)
}
fn space_if_needed<T: Data>(flex: &mut Flex<T>, params: &Params) {
match params.spacers {
Spacers::None => (),
Spacers::Default => flex.add_default_spacer(),
Spacers::Fixed => flex.add_spacer(params.spacer_size),
Spacers::Flex => flex.add_flex_spacer(1.0),
}
}
fn build_widget(state: &Params) -> Box<dyn Widget<AppState>> {
let mut flex = match state.axis {
FlexType::Column => Flex::column(),
FlexType::Row => Flex::row(),
}
.cross_axis_alignment(state.cross_alignment)
.main_axis_alignment(state.main_alignment)
.must_fill_main_axis(state.fill_major_axis);
flex.add_child(
TextBox::new()
.with_placeholder("Sample text")
.lens(DemoState::input_text),
);
space_if_needed(&mut flex, state);
flex.add_child(
Button::new("Clear").on_click(|_ctx, data: &mut DemoState, _env| {
data.input_text.clear();
data.enabled = false;
data.volume = 0.0;
}),
);
space_if_needed(&mut flex, state);
flex.add_child(
Label::new(|data: &DemoState, _: &Env| data.input_text.clone()).with_text_size(32.0),
);
space_if_needed(&mut flex, state);
flex.add_child(Checkbox::new("Demo").lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Switch::new().lens(DemoState::enabled));
space_if_needed(&mut flex, state);
flex.add_child(Slider::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(ProgressBar::new().lens(DemoState::volume));
space_if_needed(&mut flex, state);
flex.add_child(
Stepper::new()
.with_range(0.0, 1.0)
.with_step(0.1)
.with_wraparound(true)
.lens(DemoState::volume),
);
let mut flex = SizedBox::new(flex);
if state.fix_minor_axis {
match state.axis {
FlexType::Row => flex = flex.height(200.),
FlexType::Column => flex = flex.width(200.),
}
}
if state.fix_major_axis {
match state.axis {
FlexType::Row => flex = flex.width(600.),
FlexType::Column => flex = flex.height(300.),
}
}
let flex = flex
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0)
.lens(AppState::demo_state);
if state.debug_layout {
flex.debug_paint_layout().boxed()
} else {
flex.boxed()
}
}
More examples
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 96 97 98 99
fn build_root_widget() -> impl Widget<AppState> {
let range = Flex::row()
.with_child(Label::dynamic(|value: &(f64, f64), _| {
format!("Value Range: {value:?}")
}))
.with_default_spacer()
.with_child(
RangeSlider::new()
.with_range(0.0, 20.0)
.with_step(1.0)
.track_color(KeyOrValue::Concrete(Color::RED))
.fix_width(250.0),
)
.lens(AppState::range);
let value = Flex::row()
.with_child(Label::dynamic(|value: &AppState, _| {
format!("Value: {:?}", value.value)
}))
.with_default_spacer()
.with_child(ViewSwitcher::new(
|data: &AppState, _| data.range,
|range, _, _| {
Slider::new()
.with_range(range.0, range.1)
.track_color(KeyOrValue::Concrete(Color::RED))
.knob_style(KnobStyle::Wedge)
.axis(Axis::Vertical)
.with_step(0.25)
.annotated(1.0, 0.25)
.fix_height(250.0)
.lens(AppState::value)
.boxed()
},
));
// arrange the two widgets vertically, with some padding
Flex::column()
.with_child(range)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(value)
.cross_axis_alignment(CrossAxisAlignment::End)
.align_vertical(UnitPoint::RIGHT)
.padding(20.0)
}
sourcefn disabled_if(
self,
disabled_if: impl Fn(&T, &Env) -> bool + 'static
) -> DisabledIf<T, Self>
fn disabled_if( self, disabled_if: impl Fn(&T, &Env) -> bool + 'static ) -> DisabledIf<T, Self>
Wrap this widget in a DisabledIf
widget.
The provided closure will determine if the widget is disabled.
See is_disabled
or set_disabled
for more info about disabled state.
Examples found in repository?
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
fn main_widget() -> impl Widget<AppData> {
Flex::column()
.with_child(named_child("text:", TextBox::new().lens(AppData::text)))
.with_default_spacer()
.with_child(
named_child("text (disabled):", TextBox::new().lens(AppData::text))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(named_child("text:", TextBox::new().lens(AppData::text)))
.with_default_spacer()
.with_child(
named_child("text (disabled):", TextBox::new().lens(AppData::text))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_default_spacer()
.with_child(
named_child(
"value (disabled):",
Slider::new().with_range(0.0, 10.0).lens(AppData::value),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child(
"value (disabled):",
Stepper::new()
.with_range(0.0, 10.0)
.with_step(0.5)
.lens(AppData::value),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child(
"option (disabled):",
Checkbox::new("option").lens(AppData::option),
)
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
named_child("option (disabled):", Switch::new().lens(AppData::option))
.disabled_if(|data, _| data.disabled),
)
.with_default_spacer()
.with_child(
Flex::row()
.with_child(
Button::new("-")
.on_click(|_, data: &mut f64, _| *data -= 1.0)
.disabled_if(|data, _| *data < 1.0),
)
.with_default_spacer()
.with_child(Label::dynamic(|data: &f64, _| data.to_string()))
.with_default_spacer()
.with_child(
Button::new("+")
.on_click(|_, data: &mut f64, _| *data += 1.0)
.disabled_if(|data, _| *data > 9.0),
)
.lens(AppData::value)
.disabled_if(|data: &AppData, _| data.disabled),
)
.with_default_spacer()
.with_default_spacer()
.with_default_spacer()
.with_child(Checkbox::new("disabled").lens(AppData::disabled))
.with_default_spacer()
.cross_axis_alignment(CrossAxisAlignment::End)
.align_horizontal(UnitPoint::CENTER)
}