// Copyright 2020 The xi-editor 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. 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, LensExt, LocalizedString, PlatformError, WidgetId, WindowDesc, }; const DEFAULT_SPACER_SIZE: f64 = 8.; #[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, Flex, Fixed, } #[derive(Clone, Copy, PartialEq, Data)] enum FlexType { Row, Column, } /// builds a child Flex widget from some paramaters. 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:").padding(5.0)) .with_child( RadioGroup::new(vec![("Row", FlexType::Row), ("Column", FlexType::Column)]) .lens(Params::axis), ), ) .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("CrossAxis:").padding(5.0)) .with_child( RadioGroup::new(vec![ ("Start", CrossAxisAlignment::Start), ("Center", CrossAxisAlignment::Center), ("End", CrossAxisAlignment::End), ]) .lens(Params::cross_alignment), ), ) .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("MainAxis:").padding(5.0)) .with_child( RadioGroup::new(vec![ ("Start", MainAxisAlignment::Start), ("Center", MainAxisAlignment::Center), ("End", MainAxisAlignment::End), ("Between", MainAxisAlignment::SpaceBetween), ("Evenly", MainAxisAlignment::SpaceEvenly), ("Around", MainAxisAlignment::SpaceAround), ]) .lens(Params::main_alignment), ), ) .with_child(make_spacer_select()) .with_child( Flex::column() .cross_axis_alignment(CrossAxisAlignment::Start) .with_child(Label::new("Misc:").padding((0., 0., 0., 10.))) .with_child(Checkbox::new("Debug layout").lens(Params::debug_layout)) .with_spacer(10.) .with_child(Checkbox::new("Fill main axis").lens(Params::fill_major_axis)) .with_spacer(10.) .with_child(Checkbox::new("Fix minor axis size").lens(Params::fix_minor_axis)) .with_spacer(10.) .with_child(Checkbox::new("Fix major axis size").lens(Params::fix_major_axis)) .padding(5.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:").padding(5.0)) .with_child( RadioGroup::new(vec![ ("None", Spacers::None), ("Flex", Spacers::Flex), ("Fixed:", Spacers::Fixed), ]) .lens(Params::spacers), ) .with_child( Flex::row() .with_child( TextBox::new() .parse() .lens( Params::spacer_size .map(|x| Some(*x), |x, y| *x = y.unwrap_or(DEFAULT_SPACER_SIZE)), ) .fix_width(60.0), ) .with_child( Stepper::new() .with_range(2.0, 50.0) .with_step(2.0) .lens(Params::spacer_size), ) .padding((8.0, 5.0)), ) } fn space_if_needed(flex: &mut Flex, params: &Params) { match params.spacers { Spacers::None => (), Spacers::Fixed => flex.add_spacer(params.spacer_size), Spacers::Flex => flex.add_flex_spacer(1.0), } } fn build_widget(state: &Params) -> Box> { let 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); let mut flex = flex.with_child(TextBox::new().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() })); space_if_needed(&mut flex, state); flex.add_child(Checkbox::new("Demo").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), ); space_if_needed(&mut flex, state); flex.add_child(Switch::new().lens(DemoState::enabled)); let flex = flex .background(Color::rgba8(0, 0, 0xFF, 0x30)) .lens(AppState::demo_state); 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.), } } 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_spacer(20.) .with_flex_child(Rebuilder::new(), 1.0) .padding(10.0) } pub fn main() -> Result<(), PlatformError> { let main_window = WindowDesc::new(make_ui) .window_size((620., 600.00)) .with_min_size((620., 265.00)) .title(LocalizedString::new("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, }; let data = AppState { demo_state, params }; AppLauncher::with_window(main_window) .use_simple_logger() .launch(data)?; Ok(()) }