// Copyright 2020 The Druid Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Demonstrates alignment of children in the flex container. //! This example showcases the full set of functionality of flex, giving you //! knobs to change all the parameters. 99% of the time you will want to //! hard-code these parameters, which will simplify your code considerably. // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] use druid::text::ParseFormatter; use druid::widget::prelude::*; use druid::widget::{ Button, Checkbox, CrossAxisAlignment, Flex, Label, MainAxisAlignment, ProgressBar, RadioGroup, SizedBox, Slider, Stepper, Switch, TextBox, WidgetExt, }; use druid::{AppLauncher, Color, Data, Lens, WidgetId, WindowDesc}; const DEFAULT_SPACER_SIZE: f64 = 8.; const SPACER_OPTIONS: [(&str, Spacers); 4] = [ ("None", Spacers::None), ("Default", Spacers::Default), ("Flex", Spacers::Flex), ("Fixed:", Spacers::Fixed), ]; const MAIN_AXIS_ALIGNMENT_OPTIONS: [(&str, MainAxisAlignment); 6] = [ ("Start", MainAxisAlignment::Start), ("Center", MainAxisAlignment::Center), ("End", MainAxisAlignment::End), ("Between", MainAxisAlignment::SpaceBetween), ("Evenly", MainAxisAlignment::SpaceEvenly), ("Around", MainAxisAlignment::SpaceAround), ]; const CROSS_AXIS_ALIGNMENT_OPTIONS: [(&str, CrossAxisAlignment); 5] = [ ("Start", CrossAxisAlignment::Start), ("Center", CrossAxisAlignment::Center), ("End", CrossAxisAlignment::End), ("Baseline", CrossAxisAlignment::Baseline), ("Fill", CrossAxisAlignment::Fill), ]; const FLEX_TYPE_OPTIONS: [(&str, FlexType); 2] = [("Row", FlexType::Row), ("Column", FlexType::Column)]; #[derive(Clone, Data, Lens)] struct AppState { demo_state: DemoState, params: Params, } #[derive(Clone, Data, Lens)] struct DemoState { pub input_text: String, pub enabled: bool, volume: f64, } #[derive(Clone, Data, Lens)] struct Params { axis: FlexType, cross_alignment: CrossAxisAlignment, main_alignment: MainAxisAlignment, fill_major_axis: bool, debug_layout: bool, fix_minor_axis: bool, fix_major_axis: bool, spacers: Spacers, spacer_size: f64, } #[derive(Clone, Copy, PartialEq, Data)] enum Spacers { None, Default, Flex, Fixed, } #[derive(Clone, Copy, PartialEq, Data)] enum FlexType { Row, Column, } /// builds a child Flex widget from some parameters. struct Rebuilder { inner: Box>, } impl Rebuilder { fn new() -> Rebuilder { Rebuilder { inner: SizedBox::empty().boxed(), } } fn rebuild_inner(&mut self, data: &AppState) { self.inner = build_widget(&data.params); } } impl Widget for Rebuilder { fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, env: &Env) { self.inner.event(ctx, event, data, env) } fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &AppState, env: &Env) { if let LifeCycle::WidgetAdded = event { self.rebuild_inner(data); } self.inner.lifecycle(ctx, event, data, env) } fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppState, data: &AppState, env: &Env) { if !old_data.params.same(&data.params) { self.rebuild_inner(data); ctx.children_changed(); } else { self.inner.update(ctx, old_data, data, env); } } fn layout( &mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &AppState, env: &Env, ) -> Size { self.inner.layout(ctx, bc, data, env) } fn paint(&mut self, ctx: &mut PaintCtx, data: &AppState, env: &Env) { self.inner.paint(ctx, data, env) } fn id(&self) -> Option { self.inner.id() } } fn make_control_row() -> impl Widget { Flex::row() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("Type:")) .with_default_spacer() .with_child(RadioGroup::column(FLEX_TYPE_OPTIONS.to_vec()).lens(Params::axis)), ) .with_default_spacer() .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("CrossAxis:")) .with_default_spacer() .with_child( RadioGroup::column(CROSS_AXIS_ALIGNMENT_OPTIONS.to_vec()) .lens(Params::cross_alignment), ), ) .with_default_spacer() .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("MainAxis:")) .with_default_spacer() .with_child( RadioGroup::column(MAIN_AXIS_ALIGNMENT_OPTIONS.to_vec()) .lens(Params::main_alignment), ), ) .with_default_spacer() .with_child(make_spacer_select()) .with_default_spacer() .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("Misc:")) .with_default_spacer() .with_child(Checkbox::new("Debug layout").lens(Params::debug_layout)) .with_default_spacer() .with_child(Checkbox::new("Fill main axis").lens(Params::fill_major_axis)) .with_default_spacer() .with_child(Checkbox::new("Fix minor axis size").lens(Params::fix_minor_axis)) .with_default_spacer() .with_child(Checkbox::new("Fix major axis size").lens(Params::fix_major_axis)), ) .padding(10.0) .border(Color::grey(0.6), 2.0) .rounded(5.0) .lens(AppState::params) } fn make_spacer_select() -> impl Widget { Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("Insert Spacers:")) .with_default_spacer() .with_child(RadioGroup::column(SPACER_OPTIONS.to_vec()).lens(Params::spacers)) .with_default_spacer() .with_child( Flex::row() .with_child( TextBox::new() .with_formatter(ParseFormatter::new()) .lens(Params::spacer_size) .fix_width(60.0), ) .with_spacer(druid::theme::WIDGET_CONTROL_COMPONENT_PADDING) .with_child( Stepper::new() .with_range(2.0, 50.0) .with_step(2.0) .lens(Params::spacer_size), ), ) } fn space_if_needed(flex: &mut Flex, params: &Params) { match params.spacers { Spacers::None => (), Spacers::Default => flex.add_default_spacer(), Spacers::Fixed => flex.add_spacer(params.spacer_size), Spacers::Flex => flex.add_flex_spacer(1.0), } } fn build_widget(state: &Params) -> Box> { 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 { 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) } pub fn main() { let main_window = WindowDesc::new(make_ui()) .window_size((720., 600.)) .with_min_size((620., 300.)) .title("Flex Container Options"); let demo_state = DemoState { input_text: "hello".into(), enabled: false, volume: 0.0, }; let params = Params { axis: FlexType::Row, cross_alignment: CrossAxisAlignment::Center, main_alignment: MainAxisAlignment::Start, debug_layout: false, fix_minor_axis: false, fix_major_axis: false, spacers: Spacers::None, spacer_size: DEFAULT_SPACER_SIZE, fill_major_axis: false, }; AppLauncher::with_window(main_window) .log_to_console() .launch(AppState { demo_state, params }) .expect("Failed to launch application"); }