pub struct Flex<T> { /* private fields */ }
Expand description
A container with either horizontal or vertical layout.
This widget is the foundation of most layouts, and is highly configurable.
Flex layout algorithm
Children of a Flex
container can have an optional flex
parameter.
Layout occurs in several passes. First we measure (calling their layout
method) our non-flex children, providing them with unbounded space on the
main axis. Next, the remaining space is divided between the flex children
according to their flex factor, and they are measured. Unlike a non-flex
child, a child with a non-zero flex factor has a maximum allowed size
on the main axis; non-flex children are allowed to choose their size first,
and freely.
If you would like a child to be forced to use up all of the flex space
passed to it, you can place it in a SizedBox
set to expand
in the
appropriate axis. There are convenience methods for this available on
WidgetExt
: expand_width
and expand_height
.
Flex or non-flex?
When should your children be flexible? With other things being equal, a flexible child has lower layout priority than a non-flexible child. Imagine, for instance, we have a row that is 30dp wide, and we have two children, both of which want to be 20dp wide. If child #1 is non-flex and child #2 is flex, the first widget will take up its 20dp, and the second widget will be constrained to 10dp.
If, instead, both widgets are flex, they will each be given equal space, and both will end up taking up 15dp.
If both are non-flex they will both take up 20dp, and will overflow the container.
-------non-flex----- -flex-----
| child #1 | child #2 |
----flex------- ----flex-------
| child #1 | child #2 |
In general, if you are using widgets that are opinionated about their size (such as most control widgets, which are designed to lay out nicely together, or text widgets that are sized to fit their text) you should make them non-flexible.
If you are trying to divide space evenly, or if you want a particular item to have access to all left over space, then you should make it flexible.
note: by default, a widget will not necessarily use all the space that
is available to it. For instance, the TextBox
widget has a default
width, and will choose this width if possible, even if more space is
available to it. If you want to force a widget to use all available space,
you should expand it, with expand_width
or expand_height
.
Options
To experiment with these options, see the flex
example in druid/examples
.
-
CrossAxisAlignment
determines how children are positioned on the cross or ‘minor’ axis. The default isCrossAxisAlignment::Center
. -
MainAxisAlignment
determines how children are positioned on the main axis; this is only meaningful if the container has more space on the main axis than is taken up by its children. -
must_fill_main_axis
determines whether the container is obliged to be maximally large on the major axis, as determined by its own constraints. If this istrue
, then the container must fill the available space on that axis; otherwise it may be smaller if its children are smaller.
Additional options can be set (or overridden) in the FlexParams
.
Examples
Construction with builder methods
use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
let my_row = Flex::row()
.cross_axis_alignment(CrossAxisAlignment::Center)
.must_fill_main_axis(true)
.with_child(Label::new("hello"))
.with_default_spacer()
.with_flex_child(Slider::new(), 1.0);
Construction with mutating methods
use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
let mut my_row = Flex::row();
my_row.set_must_fill_main_axis(true);
my_row.set_cross_axis_alignment(CrossAxisAlignment::Center);
my_row.add_child(Label::new("hello"));
my_row.add_default_spacer();
my_row.add_flex_child(Slider::new(), 1.0);
Implementations§
source§impl<T: Data> Flex<T>
impl<T: Data> Flex<T>
sourcepub fn row() -> Self
pub fn row() -> Self
Create a new horizontal stack.
The child widgets are laid out horizontally, from left to right.
Examples found in repository?
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
fn named_child(name: &str, widget: impl Widget<AppData> + 'static) -> impl Widget<AppData> {
Flex::row()
.with_child(Label::new(name))
.with_default_spacer()
.with_child(widget)
}
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)
}
More examples
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
fn flex_row<T: Data>(
w1: impl Widget<T> + 'static,
w2: impl Widget<T> + 'static,
w3: impl Widget<T> + 'static,
w4: impl Widget<T> + 'static,
) -> impl Widget<T> {
Flex::row()
.with_flex_child(w1, 1.0)
.with_spacer(1.0)
.with_flex_child(w2, 1.0)
.with_spacer(1.0)
.with_flex_child(w3, 1.0)
.with_spacer(1.0)
.with_flex_child(w4, 1.0)
}
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
fn build_app() -> impl Widget<u32> {
let mut col = Flex::column();
let rows = 30;
let cols = 30;
for i in 0..cols {
let mut row = Flex::row();
let col_progress = i as f64 / cols as f64;
for j in 0..rows {
let row_progress = j as f64 / rows as f64;
row.add_child(
Container::new(SizedBox::empty().width(200.0).height(200.0))
.background(Color::rgb(1.0 * col_progress, 1.0 * row_progress, 1.0)),
);
}
col.add_child(row);
}
Scroll::new(col)
}
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()
}
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 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
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)
}
/// 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))
}
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
fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
Application::global().quit();
});
let mut col = Flex::column();
col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, inc_button));
row.add_child(Padding::new(5.0, dec_button));
col.add_flex_child(Align::centered(row), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}
sourcepub fn column() -> Self
pub fn column() -> Self
Create a new vertical stack.
The child widgets are laid out vertically, from top to bottom.
Examples found in repository?
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
fn build_root_widget() -> impl Widget<AppState> {
Flex::column()
.with_child(interactive_area())
.with_flex_child(event_list(), 1.0)
}
/// The top part of the application, that accepts keyboard and mouse input.
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)
}
More examples
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
#[derive(Clone, Data)]
struct NumberedTabs;
impl TabsPolicy for NumberedTabs {
type Key = usize;
type Build = ();
type Input = DynamicTabData;
type LabelWidget = Label<DynamicTabData>;
type BodyWidget = Label<DynamicTabData>;
fn tabs_changed(&self, old_data: &DynamicTabData, data: &DynamicTabData) -> bool {
old_data.tabs_key() != data.tabs_key()
}
fn tabs(&self, data: &DynamicTabData) -> Vec<Self::Key> {
data.tab_labels.iter().copied().collect()
}
fn tab_info(&self, key: Self::Key, _data: &DynamicTabData) -> TabInfo<DynamicTabData> {
TabInfo::new(format!("Tab {key:?}"), true)
}
fn tab_body(&self, key: Self::Key, _data: &DynamicTabData) -> Label<DynamicTabData> {
Label::new(format!("Dynamic tab body {key:?}"))
}
fn close_tab(&self, key: Self::Key, data: &mut DynamicTabData) {
if let Some(idx) = data.tab_labels.index_of(&key) {
data.remove_tab(idx)
}
}
fn tab_label(
&self,
_key: Self::Key,
info: TabInfo<Self::Input>,
_data: &Self::Input,
) -> Self::LabelWidget {
Self::default_make_label(info)
}
}
fn build_tab_widget(tab_config: &TabConfig) -> impl Widget<AppState> {
let dyn_tabs = Tabs::for_policy(NumberedTabs)
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.lens(AppState::advanced);
let control_dynamic = Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Control dynamic tabs"))
.with_child(Button::new("Add a tab").on_click(|_c, d: &mut DynamicTabData, _e| d.add_tab()))
.with_child(Label::new(|adv: &DynamicTabData, _e: &Env| {
format!("Highest tab number is {}", adv.highest_tab)
}))
.with_spacer(20.)
.lens(AppState::advanced);
let first_static_tab = Flex::row()
.with_child(Label::new("Rename tab:"))
.with_child(TextBox::new().lens(AppState::first_tab_name));
let main_tabs = Tabs::new()
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.with_tab(
|app_state: &AppState, _: &Env| app_state.first_tab_name.to_string(),
first_static_tab,
)
.with_tab("Dynamic", control_dynamic)
.with_tab("Page 3", Label::new("Page 3 content"))
.with_tab("Page 4", Label::new("Page 4 content"))
.with_tab("Page 5", Label::new("Page 5 content"))
.with_tab("Page 6", Label::new("Page 6 content"))
.with_tab_index(1);
Split::rows(main_tabs, dyn_tabs).draggable(true)
}
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()
}
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
fn build_app() -> impl Widget<u32> {
let mut col = Flex::column();
let rows = 30;
let cols = 30;
for i in 0..cols {
let mut row = Flex::row();
let col_progress = i as f64 / cols as f64;
for j in 0..rows {
let row_progress = j as f64 / rows as f64;
row.add_child(
Container::new(SizedBox::empty().width(200.0).height(200.0))
.background(Color::rgb(1.0 * col_progress, 1.0 * row_progress, 1.0)),
);
}
col.add_child(row);
}
Scroll::new(col)
}
- examples/either.rs
- examples/textbox.rs
- examples/hello.rs
- examples/lens.rs
- examples/blocking_function.rs
- examples/multiwin.rs
- examples/open_save.rs
- examples/switches.rs
- examples/slider.rs
- examples/text.rs
- examples/identity.rs
- examples/transparency.rs
- examples/calc.rs
- examples/sub_window.rs
- examples/panels.rs
- examples/flex.rs
- examples/view_switcher.rs
- examples/layout.rs
- examples/game_of_life.rs
- examples/disabled.rs
- examples/styled_text.rs
- examples/list.rs
sourcepub fn cross_axis_alignment(self, alignment: CrossAxisAlignment) -> Self
pub fn cross_axis_alignment(self, alignment: CrossAxisAlignment) -> Self
Builder-style method for specifying the childrens’ CrossAxisAlignment
.
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
#[derive(Clone, Data)]
struct NumberedTabs;
impl TabsPolicy for NumberedTabs {
type Key = usize;
type Build = ();
type Input = DynamicTabData;
type LabelWidget = Label<DynamicTabData>;
type BodyWidget = Label<DynamicTabData>;
fn tabs_changed(&self, old_data: &DynamicTabData, data: &DynamicTabData) -> bool {
old_data.tabs_key() != data.tabs_key()
}
fn tabs(&self, data: &DynamicTabData) -> Vec<Self::Key> {
data.tab_labels.iter().copied().collect()
}
fn tab_info(&self, key: Self::Key, _data: &DynamicTabData) -> TabInfo<DynamicTabData> {
TabInfo::new(format!("Tab {key:?}"), true)
}
fn tab_body(&self, key: Self::Key, _data: &DynamicTabData) -> Label<DynamicTabData> {
Label::new(format!("Dynamic tab body {key:?}"))
}
fn close_tab(&self, key: Self::Key, data: &mut DynamicTabData) {
if let Some(idx) = data.tab_labels.index_of(&key) {
data.remove_tab(idx)
}
}
fn tab_label(
&self,
_key: Self::Key,
info: TabInfo<Self::Input>,
_data: &Self::Input,
) -> Self::LabelWidget {
Self::default_make_label(info)
}
}
fn build_tab_widget(tab_config: &TabConfig) -> impl Widget<AppState> {
let dyn_tabs = Tabs::for_policy(NumberedTabs)
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.lens(AppState::advanced);
let control_dynamic = Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Control dynamic tabs"))
.with_child(Button::new("Add a tab").on_click(|_c, d: &mut DynamicTabData, _e| d.add_tab()))
.with_child(Label::new(|adv: &DynamicTabData, _e: &Env| {
format!("Highest tab number is {}", adv.highest_tab)
}))
.with_spacer(20.)
.lens(AppState::advanced);
let first_static_tab = Flex::row()
.with_child(Label::new("Rename tab:"))
.with_child(TextBox::new().lens(AppState::first_tab_name));
let main_tabs = Tabs::new()
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.with_tab(
|app_state: &AppState, _: &Env| app_state.first_tab_name.to_string(),
first_static_tab,
)
.with_tab("Dynamic", control_dynamic)
.with_tab("Page 3", Label::new("Page 3 content"))
.with_tab("Page 4", Label::new("Page 4 content"))
.with_tab("Page 5", Label::new("Page 5 content"))
.with_tab("Page 6", Label::new("Page 6 content"))
.with_tab_index(1);
Split::rows(main_tabs, dyn_tabs).draggable(true)
}
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)
}
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()
}
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 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)
}
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)
}
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)
}
sourcepub fn main_axis_alignment(self, alignment: MainAxisAlignment) -> Self
pub fn main_axis_alignment(self, alignment: MainAxisAlignment) -> Self
Builder-style method for specifying the childrens’ MainAxisAlignment
.
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
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()
}
}
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()
}
sourcepub fn must_fill_main_axis(self, fill: bool) -> Self
pub fn must_fill_main_axis(self, fill: bool) -> Self
Builder-style method for setting whether the container must expand to fill the available space on its main axis.
If any children have flex then this container will expand to fill all available space on its main axis; But if no children are flex, this flag determines whether or not the container should shrink to fit, or must expand to fill.
If it expands, and there is extra space left over, that space is
distributed in accordance with the MainAxisAlignment
.
The default value is false
.
Examples found in repository?
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 313 314 315 316 317 318 319 320 321
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()
}
}
fn make_ui() -> impl Widget<AppState> {
Flex::column()
.must_fill_main_axis(true)
.with_child(make_control_row())
.with_default_spacer()
.with_flex_child(Rebuilder::new().center(), 1.0)
.padding(10.0)
}
sourcepub fn with_child(self, child: impl Widget<T> + 'static) -> Self
pub fn with_child(self, child: impl Widget<T> + 'static) -> Self
Builder-style variant of add_child
.
Convenient for assembling a group of widgets in a single expression.
Examples found in repository?
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 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
fn build_root_widget() -> impl Widget<AppState> {
Flex::column()
.with_child(interactive_area())
.with_flex_child(event_list(), 1.0)
}
/// The top part of the application, that accepts keyboard and mouse input.
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)
}
/// 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))
}
More examples
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
fn named_child(name: &str, widget: impl Widget<AppData> + 'static) -> impl Widget<AppData> {
Flex::row()
.with_child(Label::new(name))
.with_default_spacer()
.with_child(widget)
}
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)
}
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
#[derive(Clone, Data)]
struct NumberedTabs;
impl TabsPolicy for NumberedTabs {
type Key = usize;
type Build = ();
type Input = DynamicTabData;
type LabelWidget = Label<DynamicTabData>;
type BodyWidget = Label<DynamicTabData>;
fn tabs_changed(&self, old_data: &DynamicTabData, data: &DynamicTabData) -> bool {
old_data.tabs_key() != data.tabs_key()
}
fn tabs(&self, data: &DynamicTabData) -> Vec<Self::Key> {
data.tab_labels.iter().copied().collect()
}
fn tab_info(&self, key: Self::Key, _data: &DynamicTabData) -> TabInfo<DynamicTabData> {
TabInfo::new(format!("Tab {key:?}"), true)
}
fn tab_body(&self, key: Self::Key, _data: &DynamicTabData) -> Label<DynamicTabData> {
Label::new(format!("Dynamic tab body {key:?}"))
}
fn close_tab(&self, key: Self::Key, data: &mut DynamicTabData) {
if let Some(idx) = data.tab_labels.index_of(&key) {
data.remove_tab(idx)
}
}
fn tab_label(
&self,
_key: Self::Key,
info: TabInfo<Self::Input>,
_data: &Self::Input,
) -> Self::LabelWidget {
Self::default_make_label(info)
}
}
fn build_tab_widget(tab_config: &TabConfig) -> impl Widget<AppState> {
let dyn_tabs = Tabs::for_policy(NumberedTabs)
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.lens(AppState::advanced);
let control_dynamic = Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Control dynamic tabs"))
.with_child(Button::new("Add a tab").on_click(|_c, d: &mut DynamicTabData, _e| d.add_tab()))
.with_child(Label::new(|adv: &DynamicTabData, _e: &Env| {
format!("Highest tab number is {}", adv.highest_tab)
}))
.with_spacer(20.)
.lens(AppState::advanced);
let first_static_tab = Flex::row()
.with_child(Label::new("Rename tab:"))
.with_child(TextBox::new().lens(AppState::first_tab_name));
let main_tabs = Tabs::new()
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.with_tab(
|app_state: &AppState, _: &Env| app_state.first_tab_name.to_string(),
first_static_tab,
)
.with_tab("Dynamic", control_dynamic)
.with_tab("Page 3", Label::new("Page 3 content"))
.with_tab("Page 4", Label::new("Page 4 content"))
.with_tab("Page 5", Label::new("Page 5 content"))
.with_tab("Page 6", Label::new("Page 6 content"))
.with_tab_index(1);
Split::rows(main_tabs, dyn_tabs).draggable(true)
}
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)
}
- examples/lens.rs
- examples/blocking_function.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/flex.rs
- examples/view_switcher.rs
- examples/layout.rs
- examples/game_of_life.rs
- examples/styled_text.rs
- examples/list.rs
sourcepub fn with_flex_child(
self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>
) -> Self
pub fn with_flex_child( self, child: impl Widget<T> + 'static, params: impl Into<FlexParams> ) -> Self
Builder-style method to add a flexible child to the container.
This method is used when you need more control over the behaviour
of the widget you are adding. In the general case, this likely
means giving that child a ‘flex factor’, but it could also mean
giving the child a custom CrossAxisAlignment
, or a combination
of the two.
This function takes a child widget and FlexParams
; importantly
you can pass in a float as your FlexParams
in most cases.
For the non-builder variant, see add_flex_child
.
Examples
use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
let my_row = Flex::row()
.with_flex_child(Slider::new(), 1.0)
.with_flex_child(Slider::new(), FlexParams::new(1.0, CrossAxisAlignment::End));
Examples found in repository?
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
fn build_root_widget() -> impl Widget<AppState> {
Flex::column()
.with_child(interactive_area())
.with_flex_child(event_list(), 1.0)
}
/// The top part of the application, that accepts keyboard and mouse input.
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)
}
More examples
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
fn flex_row<T: Data>(
w1: impl Widget<T> + 'static,
w2: impl Widget<T> + 'static,
w3: impl Widget<T> + 'static,
w4: impl Widget<T> + 'static,
) -> impl Widget<T> {
Flex::row()
.with_flex_child(w1, 1.0)
.with_spacer(1.0)
.with_flex_child(w2, 1.0)
.with_spacer(1.0)
.with_flex_child(w3, 1.0)
.with_spacer(1.0)
.with_flex_child(w4, 1.0)
}
fn build_calc() -> impl Widget<CalcState> {
let display = Label::new(|data: &String, _env: &_| data.clone())
.with_text_size(32.0)
.lens(CalcState::value)
.padding(5.0);
Flex::column()
.with_flex_spacer(0.2)
.with_child(display)
.with_flex_spacer(0.2)
.cross_axis_alignment(CrossAxisAlignment::End)
.with_flex_child(
flex_row(
op_button_label('c', "CE".to_string()),
op_button('C'),
op_button('⌫'),
op_button('÷'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(7),
digit_button(8),
digit_button(9),
op_button('×'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(4),
digit_button(5),
digit_button(6),
op_button('−'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(1),
digit_button(2),
digit_button(3),
op_button('+'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
op_button('±'),
digit_button(0),
op_button('.'),
op_button('='),
),
1.0,
)
}
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)
}
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)
}
sourcepub fn with_default_spacer(self) -> Self
pub fn with_default_spacer(self) -> Self
Builder-style method to add a spacer widget with a standard size.
The actual value of this spacer depends on whether this container is a row or column, as well as theme settings.
Examples found in repository?
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
fn named_child(name: &str, widget: impl Widget<AppData> + 'static) -> impl Widget<AppData> {
Flex::row()
.with_child(Label::new(name))
.with_default_spacer()
.with_child(widget)
}
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)
}
More examples
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
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)
}
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()
}
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)
}
sourcepub fn with_spacer(self, len: impl Into<KeyOrValue<f64>>) -> Self
pub fn with_spacer(self, len: impl Into<KeyOrValue<f64>>) -> Self
Builder-style method for adding a fixed-size spacer to the container.
If you are laying out standard controls in this container, you should
generally prefer to use add_default_spacer
.
Examples found in repository?
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
fn flex_row<T: Data>(
w1: impl Widget<T> + 'static,
w2: impl Widget<T> + 'static,
w3: impl Widget<T> + 'static,
w4: impl Widget<T> + 'static,
) -> impl Widget<T> {
Flex::row()
.with_flex_child(w1, 1.0)
.with_spacer(1.0)
.with_flex_child(w2, 1.0)
.with_spacer(1.0)
.with_flex_child(w3, 1.0)
.with_spacer(1.0)
.with_flex_child(w4, 1.0)
}
fn build_calc() -> impl Widget<CalcState> {
let display = Label::new(|data: &String, _env: &_| data.clone())
.with_text_size(32.0)
.lens(CalcState::value)
.padding(5.0);
Flex::column()
.with_flex_spacer(0.2)
.with_child(display)
.with_flex_spacer(0.2)
.cross_axis_alignment(CrossAxisAlignment::End)
.with_flex_child(
flex_row(
op_button_label('c', "CE".to_string()),
op_button('C'),
op_button('⌫'),
op_button('÷'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(7),
digit_button(8),
digit_button(9),
op_button('×'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(4),
digit_button(5),
digit_button(6),
op_button('−'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(1),
digit_button(2),
digit_button(3),
op_button('+'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
op_button('±'),
digit_button(0),
op_button('.'),
op_button('='),
),
1.0,
)
}
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)
}
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),
),
)
}
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)
}
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
fn build_tab_widget(tab_config: &TabConfig) -> impl Widget<AppState> {
let dyn_tabs = Tabs::for_policy(NumberedTabs)
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.lens(AppState::advanced);
let control_dynamic = Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(Label::new("Control dynamic tabs"))
.with_child(Button::new("Add a tab").on_click(|_c, d: &mut DynamicTabData, _e| d.add_tab()))
.with_child(Label::new(|adv: &DynamicTabData, _e: &Env| {
format!("Highest tab number is {}", adv.highest_tab)
}))
.with_spacer(20.)
.lens(AppState::advanced);
let first_static_tab = Flex::row()
.with_child(Label::new("Rename tab:"))
.with_child(TextBox::new().lens(AppState::first_tab_name));
let main_tabs = Tabs::new()
.with_axis(tab_config.axis)
.with_edge(tab_config.edge)
.with_transition(tab_config.transition)
.with_tab(
|app_state: &AppState, _: &Env| app_state.first_tab_name.to_string(),
first_static_tab,
)
.with_tab("Dynamic", control_dynamic)
.with_tab("Page 3", Label::new("Page 3 content"))
.with_tab("Page 4", Label::new("Page 4 content"))
.with_tab("Page 5", Label::new("Page 5 content"))
.with_tab("Page 6", Label::new("Page 6 content"))
.with_tab_index(1);
Split::rows(main_tabs, dyn_tabs).draggable(true)
}
sourcepub fn with_flex_spacer(self, flex: f64) -> Self
pub fn with_flex_spacer(self, flex: f64) -> Self
Builder-style method for adding a flex
spacer to the container.
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
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
fn build_calc() -> impl Widget<CalcState> {
let display = Label::new(|data: &String, _env: &_| data.clone())
.with_text_size(32.0)
.lens(CalcState::value)
.padding(5.0);
Flex::column()
.with_flex_spacer(0.2)
.with_child(display)
.with_flex_spacer(0.2)
.cross_axis_alignment(CrossAxisAlignment::End)
.with_flex_child(
flex_row(
op_button_label('c', "CE".to_string()),
op_button('C'),
op_button('⌫'),
op_button('÷'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(7),
digit_button(8),
digit_button(9),
op_button('×'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(4),
digit_button(5),
digit_button(6),
op_button('−'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
digit_button(1),
digit_button(2),
digit_button(3),
op_button('+'),
),
1.0,
)
.with_spacer(1.0)
.with_flex_child(
flex_row(
op_button('±'),
digit_button(0),
op_button('.'),
op_button('='),
),
1.0,
)
}
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
fn build_root_widget() -> impl Widget<AppState> {
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)
}
let axis_picker = group(
"Tab bar axis",
RadioGroup::column(vec![
("Horizontal", Axis::Horizontal),
("Vertical", Axis::Vertical),
])
.lens(TabConfig::axis),
);
let cross_picker = group(
"Tab bar edge",
RadioGroup::column(vec![
("Leading", TabsEdge::Leading),
("Trailing", TabsEdge::Trailing),
])
.lens(TabConfig::edge),
);
let transit_picker = group(
"Transition",
RadioGroup::column(vec![
("Instant", TabsTransition::Instant),
(
"Slide",
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as u64),
),
])
.lens(TabConfig::transition),
);
let sidebar = Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(axis_picker)
.with_default_spacer()
.with_child(cross_picker)
.with_default_spacer()
.with_child(transit_picker)
.with_flex_spacer(1.)
.fix_width(200.0)
.lens(AppState::tab_config);
let vs = ViewSwitcher::new(
|app_s: &AppState, _| app_s.tab_config.clone(),
|tc: &TabConfig, _, _| Box::new(build_tab_widget(tc)),
);
Flex::row().with_child(sidebar).with_flex_child(vs, 1.0)
}
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()
}
sourcepub fn set_cross_axis_alignment(&mut self, alignment: CrossAxisAlignment)
pub fn set_cross_axis_alignment(&mut self, alignment: CrossAxisAlignment)
Set the childrens’ CrossAxisAlignment
.
sourcepub fn set_main_axis_alignment(&mut self, alignment: MainAxisAlignment)
pub fn set_main_axis_alignment(&mut self, alignment: MainAxisAlignment)
Set the childrens’ MainAxisAlignment
.
Examples found in repository?
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
fn build_widget() -> impl Widget<DemoState> {
let mut col = Flex::column();
let mut row = Flex::row();
let switch = LensWrap::new(Switch::new(), DemoState::value);
let check_box = LensWrap::new(Checkbox::new(""), DemoState::value);
let switch_label = Label::new("Setting label");
row.add_child(Padding::new(5.0, switch_label));
row.add_child(Padding::new(5.0, switch));
row.add_child(Padding::new(5.0, check_box));
let stepper = LensWrap::new(
Stepper::new()
.with_range(0.0, 10.0)
.with_step(0.5)
.with_wraparound(false),
DemoState::stepper_value,
);
let mut textbox_row = Flex::row();
// TODO: Replace Parse usage with TextBox::with_formatter
#[allow(deprecated)]
let textbox = LensWrap::new(
Parse::new(TextBox::new()),
DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)),
);
textbox_row.add_child(Padding::new(5.0, textbox));
textbox_row.add_child(Padding::new(5.0, stepper.center()));
let mut label_row = Flex::row();
let label = Label::new(|data: &DemoState, _env: &_| {
format!("Stepper value: {0:.2}", data.stepper_value)
});
label_row.add_child(Padding::new(5.0, label));
col.set_main_axis_alignment(MainAxisAlignment::Center);
col.add_child(Padding::new(5.0, row));
col.add_child(Padding::new(5.0, textbox_row));
col.add_child(Padding::new(5.0, label_row));
col.center()
}
sourcepub fn set_must_fill_main_axis(&mut self, fill: bool)
pub fn set_must_fill_main_axis(&mut self, fill: bool)
Set whether the container must expand to fill the available space on its main axis.
sourcepub fn add_child(&mut self, child: impl Widget<T> + 'static)
pub fn add_child(&mut self, child: impl Widget<T> + 'static)
Add a non-flex child widget.
See also with_child
.
Examples found in repository?
More examples
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
fn build_app() -> impl Widget<u32> {
let mut col = Flex::column();
let rows = 30;
let cols = 30;
for i in 0..cols {
let mut row = Flex::row();
let col_progress = i as f64 / cols as f64;
for j in 0..rows {
let row_progress = j as f64 / rows as f64;
row.add_child(
Container::new(SizedBox::empty().width(200.0).height(200.0))
.background(Color::rgb(1.0 * col_progress, 1.0 * row_progress, 1.0)),
);
}
col.add_child(row);
}
Scroll::new(col)
}
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
fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
Application::global().quit();
});
let mut col = Flex::column();
col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, inc_button));
row.add_child(Padding::new(5.0, dec_button));
col.add_flex_child(Align::centered(row), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}
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
fn ui_builder() -> impl Widget<String> {
let rs = FileSpec::new("Rust source", &["rs"]);
let txt = FileSpec::new("Text file", &["txt"]);
let other = FileSpec::new("Bogus file", &["foo", "bar", "baz"]);
// The options can also be generated at runtime,
// so to show that off we create a String for the default save name.
let default_save_name = String::from("MyFile.txt");
let save_dialog_options = FileDialogOptions::new()
.allowed_types(vec![rs, txt, other])
.default_type(txt)
.default_name(default_save_name)
.name_label("Target")
.title("Choose a target for this lovely file")
.button_text("Export");
let open_dialog_options = save_dialog_options
.clone()
.default_name("MySavedFile.txt")
.name_label("Source")
.title("Where did you put that file?")
.button_text("Import");
let input = TextBox::new();
let save = Button::new("Save").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_SAVE_PANEL.with(save_dialog_options.clone()))
});
let open = Button::new("Open").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_OPEN_PANEL.with(open_dialog_options.clone()))
});
let mut col = Flex::column();
col.add_child(input);
col.add_spacer(8.0);
col.add_child(save);
col.add_child(open);
Align::centered(col)
}
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 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)
}
sourcepub fn add_flex_child(
&mut self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>
)
pub fn add_flex_child( &mut self, child: impl Widget<T> + 'static, params: impl Into<FlexParams> )
Add a flexible child widget.
This method is used when you need more control over the behaviour
of the widget you are adding. In the general case, this likely
means giving that child a ‘flex factor’, but it could also mean
giving the child a custom CrossAxisAlignment
, or a combination
of the two.
This function takes a child widget and FlexParams
; importantly
you can pass in a float as your FlexParams
in most cases.
For the builder-style variant, see with_flex_child
.
Examples
use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
let mut my_row = Flex::row();
my_row.add_flex_child(Slider::new(), 1.0);
my_row.add_flex_child(Slider::new(), FlexParams::new(1.0, CrossAxisAlignment::End));
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
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
fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
Application::global().quit();
});
let mut col = Flex::column();
col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, inc_button));
row.add_child(Padding::new(5.0, dec_button));
col.add_flex_child(Align::centered(row), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}
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()
}
sourcepub fn add_default_spacer(&mut self)
pub fn add_default_spacer(&mut self)
Add a spacer widget with a standard size.
The actual value of this spacer depends on whether this container is a row or column, as well as theme settings.
Examples found in repository?
More examples
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 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)
}
sourcepub fn add_spacer(&mut self, len: impl Into<KeyOrValue<f64>>)
pub fn add_spacer(&mut self, len: impl Into<KeyOrValue<f64>>)
Add an empty spacer widget with the given size.
If you are laying out standard controls in this container, you should
generally prefer to use add_default_spacer
.
Examples found in repository?
More examples
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
fn ui_builder() -> impl Widget<String> {
let rs = FileSpec::new("Rust source", &["rs"]);
let txt = FileSpec::new("Text file", &["txt"]);
let other = FileSpec::new("Bogus file", &["foo", "bar", "baz"]);
// The options can also be generated at runtime,
// so to show that off we create a String for the default save name.
let default_save_name = String::from("MyFile.txt");
let save_dialog_options = FileDialogOptions::new()
.allowed_types(vec![rs, txt, other])
.default_type(txt)
.default_name(default_save_name)
.name_label("Target")
.title("Choose a target for this lovely file")
.button_text("Export");
let open_dialog_options = save_dialog_options
.clone()
.default_name("MySavedFile.txt")
.name_label("Source")
.title("Where did you put that file?")
.button_text("Import");
let input = TextBox::new();
let save = Button::new("Save").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_SAVE_PANEL.with(save_dialog_options.clone()))
});
let open = Button::new("Open").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_OPEN_PANEL.with(open_dialog_options.clone()))
});
let mut col = Flex::column();
col.add_child(input);
col.add_spacer(8.0);
col.add_child(save);
col.add_child(open);
Align::centered(col)
}
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 96 97 98 99 100 101 102
fn make_ui() -> impl Widget<AppState> {
let mut switcher_column = Flex::column();
switcher_column.add_child(
Label::new(|data: &u32, _env: &Env| format!("Current view: {data}"))
.lens(AppState::current_view),
);
for i in 0..6 {
switcher_column.add_spacer(80.);
switcher_column.add_child(
Button::new(format!("View {i}"))
.on_click(move |_event, data: &mut u32, _env| {
*data = i;
})
.lens(AppState::current_view),
);
}
let view_switcher = ViewSwitcher::new(
|data: &AppState, _env| data.current_view,
|selector, _data, _env| match selector {
0 => Box::new(Label::new("Simple Label").center()),
1 => Box::new(
Button::new("Simple Button").on_click(|_event, _data, _env| {
println!("Simple button clicked!");
}),
),
2 => Box::new(
Button::new("Another Simple Button").on_click(|_event, _data, _env| {
println!("Another simple button clicked!");
}),
),
3 => Box::new(
Flex::column()
.with_flex_child(Label::new("Here is a label").center(), 1.0)
.with_flex_child(
Button::new("Button").on_click(|_event, _data, _env| {
println!("Complex button clicked!");
}),
1.0,
)
.with_flex_child(TextBox::new().lens(AppState::current_text), 1.0)
.with_flex_child(
Label::new(|data: &String, _env: &Env| format!("Value entered: {data}"))
.lens(AppState::current_text),
1.0,
),
),
4 => Box::new(
Split::columns(
Label::new("Left split").center(),
Label::new("Right split").center(),
)
.draggable(true),
),
_ => Box::new(Label::new("Unknown").center()),
},
);
Flex::row()
.with_child(switcher_column)
.with_flex_child(view_switcher, 1.0)
}
sourcepub fn add_flex_spacer(&mut self, flex: f64)
pub fn add_flex_spacer(&mut self, flex: f64)
Add an empty spacer widget with a specific flex
factor.