1#![windows_subsystem = "windows"]
22
23use druid::text::ParseFormatter;
24use druid::widget::prelude::*;
25use druid::widget::{
26 Button, Checkbox, CrossAxisAlignment, Flex, Label, MainAxisAlignment, ProgressBar, RadioGroup,
27 SizedBox, Slider, Stepper, Switch, TextBox, WidgetExt,
28};
29use druid::{AppLauncher, Color, Data, Lens, WidgetId, WindowDesc};
30
31const DEFAULT_SPACER_SIZE: f64 = 8.;
32const SPACER_OPTIONS: [(&str, Spacers); 4] = [
33 ("None", Spacers::None),
34 ("Default", Spacers::Default),
35 ("Flex", Spacers::Flex),
36 ("Fixed:", Spacers::Fixed),
37];
38const MAIN_AXIS_ALIGNMENT_OPTIONS: [(&str, MainAxisAlignment); 6] = [
39 ("Start", MainAxisAlignment::Start),
40 ("Center", MainAxisAlignment::Center),
41 ("End", MainAxisAlignment::End),
42 ("Between", MainAxisAlignment::SpaceBetween),
43 ("Evenly", MainAxisAlignment::SpaceEvenly),
44 ("Around", MainAxisAlignment::SpaceAround),
45];
46const CROSS_AXIS_ALIGNMENT_OPTIONS: [(&str, CrossAxisAlignment); 5] = [
47 ("Start", CrossAxisAlignment::Start),
48 ("Center", CrossAxisAlignment::Center),
49 ("End", CrossAxisAlignment::End),
50 ("Baseline", CrossAxisAlignment::Baseline),
51 ("Fill", CrossAxisAlignment::Fill),
52];
53const FLEX_TYPE_OPTIONS: [(&str, FlexType); 2] =
54 [("Row", FlexType::Row), ("Column", FlexType::Column)];
55
56#[derive(Clone, Data, Lens)]
57struct AppState {
58 demo_state: DemoState,
59 params: Params,
60}
61
62#[derive(Clone, Data, Lens)]
63struct DemoState {
64 pub input_text: String,
65 pub enabled: bool,
66 volume: f64,
67}
68
69#[derive(Clone, Data, Lens)]
70struct Params {
71 axis: FlexType,
72 cross_alignment: CrossAxisAlignment,
73 main_alignment: MainAxisAlignment,
74 fill_major_axis: bool,
75 debug_layout: bool,
76 fix_minor_axis: bool,
77 fix_major_axis: bool,
78 spacers: Spacers,
79 spacer_size: f64,
80}
81
82#[derive(Clone, Copy, PartialEq, Data)]
83enum Spacers {
84 None,
85 Default,
86 Flex,
87 Fixed,
88}
89
90#[derive(Clone, Copy, PartialEq, Data)]
91enum FlexType {
92 Row,
93 Column,
94}
95
96struct Rebuilder {
98 inner: Box<dyn Widget<AppState>>,
99}
100
101impl Rebuilder {
102 fn new() -> Rebuilder {
103 Rebuilder {
104 inner: SizedBox::empty().boxed(),
105 }
106 }
107
108 fn rebuild_inner(&mut self, data: &AppState) {
109 self.inner = build_widget(&data.params);
110 }
111}
112
113impl Widget<AppState> for Rebuilder {
114 fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, env: &Env) {
115 self.inner.event(ctx, event, data, env)
116 }
117
118 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &AppState, env: &Env) {
119 if let LifeCycle::WidgetAdded = event {
120 self.rebuild_inner(data);
121 }
122 self.inner.lifecycle(ctx, event, data, env)
123 }
124
125 fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppState, data: &AppState, env: &Env) {
126 if !old_data.params.same(&data.params) {
127 self.rebuild_inner(data);
128 ctx.children_changed();
129 } else {
130 self.inner.update(ctx, old_data, data, env);
131 }
132 }
133
134 fn layout(
135 &mut self,
136 ctx: &mut LayoutCtx,
137 bc: &BoxConstraints,
138 data: &AppState,
139 env: &Env,
140 ) -> Size {
141 self.inner.layout(ctx, bc, data, env)
142 }
143
144 fn paint(&mut self, ctx: &mut PaintCtx, data: &AppState, env: &Env) {
145 self.inner.paint(ctx, data, env)
146 }
147
148 fn id(&self) -> Option<WidgetId> {
149 self.inner.id()
150 }
151}
152
153fn make_control_row() -> impl Widget<AppState> {
154 Flex::row()
155 .cross_axis_alignment(CrossAxisAlignment::Start)
156 .with_child(
157 Flex::column()
158 .cross_axis_alignment(CrossAxisAlignment::Start)
159 .with_child(Label::new("Type:"))
160 .with_default_spacer()
161 .with_child(RadioGroup::column(FLEX_TYPE_OPTIONS.to_vec()).lens(Params::axis)),
162 )
163 .with_default_spacer()
164 .with_child(
165 Flex::column()
166 .cross_axis_alignment(CrossAxisAlignment::Start)
167 .with_child(Label::new("CrossAxis:"))
168 .with_default_spacer()
169 .with_child(
170 RadioGroup::column(CROSS_AXIS_ALIGNMENT_OPTIONS.to_vec())
171 .lens(Params::cross_alignment),
172 ),
173 )
174 .with_default_spacer()
175 .with_child(
176 Flex::column()
177 .cross_axis_alignment(CrossAxisAlignment::Start)
178 .with_child(Label::new("MainAxis:"))
179 .with_default_spacer()
180 .with_child(
181 RadioGroup::column(MAIN_AXIS_ALIGNMENT_OPTIONS.to_vec())
182 .lens(Params::main_alignment),
183 ),
184 )
185 .with_default_spacer()
186 .with_child(make_spacer_select())
187 .with_default_spacer()
188 .with_child(
189 Flex::column()
190 .cross_axis_alignment(CrossAxisAlignment::Start)
191 .with_child(Label::new("Misc:"))
192 .with_default_spacer()
193 .with_child(Checkbox::new("Debug layout").lens(Params::debug_layout))
194 .with_default_spacer()
195 .with_child(Checkbox::new("Fill main axis").lens(Params::fill_major_axis))
196 .with_default_spacer()
197 .with_child(Checkbox::new("Fix minor axis size").lens(Params::fix_minor_axis))
198 .with_default_spacer()
199 .with_child(Checkbox::new("Fix major axis size").lens(Params::fix_major_axis)),
200 )
201 .padding(10.0)
202 .border(Color::grey(0.6), 2.0)
203 .rounded(5.0)
204 .lens(AppState::params)
205}
206
207fn make_spacer_select() -> impl Widget<Params> {
208 Flex::column()
209 .cross_axis_alignment(CrossAxisAlignment::Start)
210 .with_child(Label::new("Insert Spacers:"))
211 .with_default_spacer()
212 .with_child(RadioGroup::column(SPACER_OPTIONS.to_vec()).lens(Params::spacers))
213 .with_default_spacer()
214 .with_child(
215 Flex::row()
216 .with_child(
217 TextBox::new()
218 .with_formatter(ParseFormatter::new())
219 .lens(Params::spacer_size)
220 .fix_width(60.0),
221 )
222 .with_spacer(druid::theme::WIDGET_CONTROL_COMPONENT_PADDING)
223 .with_child(
224 Stepper::new()
225 .with_range(2.0, 50.0)
226 .with_step(2.0)
227 .lens(Params::spacer_size),
228 ),
229 )
230}
231
232fn space_if_needed<T: Data>(flex: &mut Flex<T>, params: &Params) {
233 match params.spacers {
234 Spacers::None => (),
235 Spacers::Default => flex.add_default_spacer(),
236 Spacers::Fixed => flex.add_spacer(params.spacer_size),
237 Spacers::Flex => flex.add_flex_spacer(1.0),
238 }
239}
240
241fn build_widget(state: &Params) -> Box<dyn Widget<AppState>> {
242 let mut flex = match state.axis {
243 FlexType::Column => Flex::column(),
244 FlexType::Row => Flex::row(),
245 }
246 .cross_axis_alignment(state.cross_alignment)
247 .main_axis_alignment(state.main_alignment)
248 .must_fill_main_axis(state.fill_major_axis);
249
250 flex.add_child(
251 TextBox::new()
252 .with_placeholder("Sample text")
253 .lens(DemoState::input_text),
254 );
255 space_if_needed(&mut flex, state);
256
257 flex.add_child(
258 Button::new("Clear").on_click(|_ctx, data: &mut DemoState, _env| {
259 data.input_text.clear();
260 data.enabled = false;
261 data.volume = 0.0;
262 }),
263 );
264
265 space_if_needed(&mut flex, state);
266
267 flex.add_child(
268 Label::new(|data: &DemoState, _: &Env| data.input_text.clone()).with_text_size(32.0),
269 );
270 space_if_needed(&mut flex, state);
271 flex.add_child(Checkbox::new("Demo").lens(DemoState::enabled));
272 space_if_needed(&mut flex, state);
273 flex.add_child(Switch::new().lens(DemoState::enabled));
274 space_if_needed(&mut flex, state);
275 flex.add_child(Slider::new().lens(DemoState::volume));
276 space_if_needed(&mut flex, state);
277 flex.add_child(ProgressBar::new().lens(DemoState::volume));
278 space_if_needed(&mut flex, state);
279 flex.add_child(
280 Stepper::new()
281 .with_range(0.0, 1.0)
282 .with_step(0.1)
283 .with_wraparound(true)
284 .lens(DemoState::volume),
285 );
286
287 let mut flex = SizedBox::new(flex);
288 if state.fix_minor_axis {
289 match state.axis {
290 FlexType::Row => flex = flex.height(200.),
291 FlexType::Column => flex = flex.width(200.),
292 }
293 }
294 if state.fix_major_axis {
295 match state.axis {
296 FlexType::Row => flex = flex.width(600.),
297 FlexType::Column => flex = flex.height(300.),
298 }
299 }
300
301 let flex = flex
302 .padding(8.0)
303 .border(Color::grey(0.6), 2.0)
304 .rounded(5.0)
305 .lens(AppState::demo_state);
306
307 if state.debug_layout {
308 flex.debug_paint_layout().boxed()
309 } else {
310 flex.boxed()
311 }
312}
313
314fn make_ui() -> impl Widget<AppState> {
315 Flex::column()
316 .must_fill_main_axis(true)
317 .with_child(make_control_row())
318 .with_default_spacer()
319 .with_flex_child(Rebuilder::new().center(), 1.0)
320 .padding(10.0)
321}
322
323pub fn main() {
324 let main_window = WindowDesc::new(make_ui())
325 .window_size((720., 600.))
326 .with_min_size((620., 300.))
327 .title("Flex Container Options");
328
329 let demo_state = DemoState {
330 input_text: "hello".into(),
331 enabled: false,
332 volume: 0.0,
333 };
334
335 let params = Params {
336 axis: FlexType::Row,
337 cross_alignment: CrossAxisAlignment::Center,
338 main_alignment: MainAxisAlignment::Start,
339 debug_layout: false,
340 fix_minor_axis: false,
341 fix_major_axis: false,
342 spacers: Spacers::None,
343 spacer_size: DEFAULT_SPACER_SIZE,
344 fill_major_axis: false,
345 };
346
347 AppLauncher::with_window(main_window)
348 .log_to_console()
349 .launch(AppState { demo_state, params })
350 .expect("Failed to launch application");
351}