Skip to content
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,8 +672,9 @@ 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
[#1698]: https://github.com/linebender/druid/pull/1698
[#1695]: https://github.com/linebender/druid/pull/1695
[#1698]: https://github.com/linebender/druid/pull/1698
[#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
93 changes: 93 additions & 0 deletions druid/examples/disabled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use druid::widget::{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_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
50 changes: 50 additions & 0 deletions druid/src/widget/disable_if.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.
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.
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
14 changes: 13 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,18 @@ 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 the disabled state.
///
/// [`is_disabled`]: struct.EventCtx.html#method.is_disabled
/// [`set_disabled`]: struct.EventCtx.html#method.set_disabled
/// [`DisabledIf`]: widget/struct.DisabledIf.html
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