From 7e20da97efd5796154d0b5570f307c49f1f4e307 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 23 Nov 2019 11:03:39 -0800 Subject: [PATCH] Widget adaptor for string parsing --- druid/examples/parse.rs | 37 +++++++++++++++++++ druid/src/widget/mod.rs | 3 ++ druid/src/widget/parse.rs | 65 ++++++++++++++++++++++++++++++++++ druid/src/widget/widget_ext.rs | 10 +++++- 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 druid/examples/parse.rs create mode 100644 druid/src/widget/parse.rs diff --git a/druid/examples/parse.rs b/druid/examples/parse.rs new file mode 100644 index 0000000000..0c620179bd --- /dev/null +++ b/druid/examples/parse.rs @@ -0,0 +1,37 @@ +// Copyright 2019 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. + +use druid::widget::{Align, Column, DynLabel, Padding, Parse, TextBox}; +use druid::{AppLauncher, Widget, WindowDesc}; + +fn main() { + let main_window = WindowDesc::new(ui_builder); + let data = Some(0); + AppLauncher::with_window(main_window) + .use_simple_logger() + .launch(data) + .expect("launch failed"); +} + +fn ui_builder() -> impl Widget> { + let label = DynLabel::new(|data: &Option, _env| { + data.map_or_else(|| "Invalid input".into(), |x| x.to_string()) + }); + let input = Parse::new(TextBox::new()); + + let mut col = Column::new(); + col.add_child(Align::centered(Padding::new(5.0, label)), 1.0); + col.add_child(Padding::new(5.0, input), 1.0); + col +} diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index df0bb589aa..e1f177981c 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -70,3 +70,6 @@ pub use widget_ext::WidgetExt; mod list; pub use crate::widget::list::{List, ListIter}; + +mod parse; +pub use crate::widget::parse::Parse; diff --git a/druid/src/widget/parse.rs b/druid/src/widget/parse.rs new file mode 100644 index 0000000000..f0e227fa9f --- /dev/null +++ b/druid/src/widget/parse.rs @@ -0,0 +1,65 @@ +use std::fmt::Display; +use std::mem; +use std::str::FromStr; + +use crate::kurbo::Size; +use crate::{ + BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, UpdateCtx, Widget, +}; + +/// Converts a `Widget` to a `Widget>`, mapping parse errors to None +pub struct Parse { + widget: T, + state: String, +} + +impl Parse { + pub fn new(widget: T) -> Self { + Self { + widget, + state: String::new(), + } + } +} + +impl> Widget> for Parse { + fn update( + &mut self, + ctx: &mut UpdateCtx, + old_data: Option<&Option>, + data: &Option, + env: &Env, + ) { + let old = match *data { + None => return, // Don't clobber the input + Some(ref x) => mem::replace(&mut self.state, x.to_string()), + }; + let old = old_data.map(|_| old); + self.widget.update(ctx, old.as_ref(), &self.state, env) + } + + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut Option, env: &Env) { + self.widget.event(ctx, event, &mut self.state, env); + *data = self.state.parse().ok(); + } + + fn layout( + &mut self, + ctx: &mut LayoutCtx, + bc: &BoxConstraints, + _data: &Option, + env: &Env, + ) -> Size { + self.widget.layout(ctx, bc, &self.state, env) + } + + fn paint( + &mut self, + paint: &mut PaintCtx, + base_state: &BaseState, + _data: &Option, + env: &Env, + ) { + self.widget.paint(paint, base_state, &self.state, env) + } +} diff --git a/druid/src/widget/widget_ext.rs b/druid/src/widget/widget_ext.rs index a36074f023..2005dceff0 100644 --- a/druid/src/widget/widget_ext.rs +++ b/druid/src/widget/widget_ext.rs @@ -17,7 +17,7 @@ use crate::kurbo::Insets; use crate::piet::{PaintBrush, UnitPoint}; -use super::{Align, Container, EnvScope, Padding, SizedBox}; +use super::{Align, Container, EnvScope, Padding, Parse, SizedBox}; use crate::{Data, Env, Lens, LensWrap, Widget}; /// A trait that provides extra methods for combining `Widget`s. @@ -116,6 +116,14 @@ pub trait WidgetExt: Widget + Sized + 'static { fn lens>(self, lens: L) -> LensWrap { LensWrap::new(self, lens) } + + /// Parse a `Widget`'s contents + fn parse(self) -> Parse + where + Self: Widget, + { + Parse::new(self) + } } impl + 'static> WidgetExt for W {}