Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DisabledIf widget #1702

Merged
merged 14 commits into from
Apr 13, 2021
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 @@ -674,9 +674,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::{
cmyr marked this conversation as resolved.
Show resolved Hide resolved
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>,
xarvic marked this conversation as resolved.
Show resolved Hide resolved
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