Skip to content

Commit

Permalink
DisabledIf widget (#1702)
Browse files Browse the repository at this point in the history
* implemented DisabledIf

* reformat

* updated CHANGELOG.md

* fix issue

* updated example

Signed-off-by: xarvic <[email protected]>

* added License, updated documentation

Signed-off-by: xarvic <[email protected]>

* reformat

Signed-off-by: xarvic <[email protected]>

* reformat

Signed-off-by: xarvic <[email protected]>

* Apply suggestions from code review

Co-authored-by: xarvic <[email protected]>
Co-authored-by: Colin Rofls <[email protected]>
  • Loading branch information
3 people authored Apr 13, 2021
1 parent c953fa9 commit 0c68463
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 3 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ You can find its changes [documented below](#070---2021-01-01).
- `LifeCycle::DisabledChanged`, `InternalLifeCycle::RouteDisabledChanged` and the `set_disabled()` and `is_disabled()`
context-methods to implement disabled ([#1632] by [@xarvic])
- `LifeCycle::BuildFocusChain` to update the focus-chain ([#1632] by [@xarvic])

- `DisabledIf` widget wrapper to disable based on the state of Data and Env ([#1702] by [@xarvic])
### Changed

- Warn on unhandled Commands ([#1533] by [@Maan2003])
Expand Down Expand Up @@ -672,9 +672,10 @@ Last release without a changelog :(
[#1677]: https://github.com/linebender/druid/pull/1677
[#1691]: https://github.com/linebender/druid/pull/1691
[#1693]: https://github.com/linebender/druid/pull/1693
[#1695]: https://github.com/linebender/druid/pull/1695
[#1696]: https://github.com/linebender/druid/pull/1696
[#1698]: https://github.com/linebender/druid/pull/1698
[#1695]: https://github.com/linebender/druid/pull/1695
[#1702]: https://github.com/linebender/druid/pull/1702

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0
Expand Down
133 changes: 133 additions & 0 deletions druid/examples/disabled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2021 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.

//! An example showing the effect of the disabled state on focus-chain and the widgets.
//! When the disabled checkbox is clicked only the widgets not marked as disabled should
//! respond. Pressing Tab should only focus widgets not marked as disabled. If a widget
//! is focused while getting disabled it should resign the focus.

use druid::widget::{
Button, Checkbox, CrossAxisAlignment, Flex, Label, Slider, Stepper, Switch, TextBox,
};
use druid::{AppLauncher, Data, Lens, LocalizedString, UnitPoint, Widget, WidgetExt, WindowDesc};

#[derive(Clone, Data, Lens)]
struct AppData {
option: bool,
text: String,
value: f64,

disabled: bool,
}

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)
}

pub fn main() {
let window = WindowDesc::new(main_widget()).title(
LocalizedString::new("disabled-demo-window-title").with_placeholder("Disabled demo"),
);
AppLauncher::with_window(window)
.log_to_console()
.launch(AppData {
option: true,
text: "a very important text!".to_string(),
value: 2.0,
disabled: false,
})
.expect("launch failed");
}
1 change: 1 addition & 0 deletions druid/examples/web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl_example!(anim);
impl_example!(calc);
impl_example!(cursor);
impl_example!(custom_widget);
impl_example!(disabled);
impl_example!(either);
impl_example!(event_viewer);
impl_example!(flex);
Expand Down
71 changes: 71 additions & 0 deletions druid/src/widget/disable_if.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2021 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.

use crate::{
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, Size, UpdateCtx, Widget, WidgetPod,
};

/// A widget wrapper which disables the inner widget if the provided closure return true.
///
/// See [`is_disabled`] or [`set_disabled`] for more info about disabled state.
///
/// [`is_disabled`]: crate::EventCtx::is_disabled
/// [`set_disabled`]: crate::EventCtx::set_disabled
pub struct DisabledIf<T, W> {
inner: WidgetPod<T, W>,
disabled_if: Box<dyn Fn(&T, &Env) -> bool>,
}

impl<T: Data, W: Widget<T>> DisabledIf<T, W> {
/// Creates a new `DisabledIf` widget with the inner widget and the closure to decide if the
/// widget should be [`disabled`].
///
/// [`disabled`]: crate::EventCtx::is_disabled
pub fn new(widget: W, disabled_if: impl Fn(&T, &Env) -> bool + 'static) -> Self {
DisabledIf {
inner: WidgetPod::new(widget),
disabled_if: Box::new(disabled_if),
}
}
}

impl<T: Data, W: Widget<T>> Widget<T> for DisabledIf<T, W> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
self.inner.event(ctx, event, data, env);
}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
if let LifeCycle::WidgetAdded = event {
ctx.set_disabled((self.disabled_if)(data, env));
}
self.inner.lifecycle(ctx, event, data, env);
}

fn update(&mut self, ctx: &mut UpdateCtx, _: &T, data: &T, env: &Env) {
ctx.set_disabled((self.disabled_if)(data, env));
self.inner.update(ctx, data, env);
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
let size = self.inner.layout(ctx, bc, data, env);
self.inner.set_origin(ctx, data, env, Point::ZERO);
ctx.set_baseline_offset(self.inner.baseline_offset());
size
}

fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.inner.paint(ctx, data, env);
}
}
2 changes: 2 additions & 0 deletions druid/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod clip_box;
mod common;
mod container;
mod controller;
mod disable_if;
mod either;
mod env_scope;
mod flex;
Expand Down Expand Up @@ -71,6 +72,7 @@ pub use clip_box::{ClipBox, Viewport};
pub use common::FillStrat;
pub use container::Container;
pub use controller::{Controller, ControllerHost};
pub use disable_if::DisabledIf;
pub use either::Either;
pub use env_scope::EnvScope;
pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment};
Expand Down
13 changes: 12 additions & 1 deletion druid/src/widget/widget_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::{
Added, Align, BackgroundBrush, Click, Container, Controller, ControllerHost, EnvScope,
IdentityWrapper, LensWrap, Padding, Parse, SizedBox, WidgetId,
};
use crate::widget::Scroll;
use crate::widget::{DisabledIf, Scroll};
use crate::{
Color, Data, Env, EventCtx, Insets, KeyOrValue, Lens, LifeCycleCtx, UnitPoint, Widget,
};
Expand Down Expand Up @@ -273,6 +273,17 @@ pub trait WidgetExt<T: Data>: Widget<T> + Sized + 'static {
fn scroll(self) -> Scroll<T, Self> {
Scroll::new(self)
}

/// Wrap this widget in a [`DisabledIf`] widget.
///
/// The provided closure will determine if the widget is disabled.
/// See [`is_disabled`] or [`set_disabled`] for more info about disabled state.
///
/// [`is_disabled`]: crate::EventCtx::is_disabled
/// [`set_disabled`]: crate::EventCtx::set_disabled
fn disabled_if(self, disabled_if: impl Fn(&T, &Env) -> bool + 'static) -> DisabledIf<T, Self> {
DisabledIf::new(self, disabled_if)
}
}

impl<T: Data, W: Widget<T> + 'static> WidgetExt<T> for W {}
Expand Down

0 comments on commit 0c68463

Please sign in to comment.