Implemented `txt_parse`, real time only.
This commit is contained in:
parent
7874213cbc
commit
91000c0feb
|
@ -46,10 +46,11 @@
|
|||
# DATA Notes
|
||||
|
||||
* Implement `txt_parse`.
|
||||
- It must use `DATA.invalidate`.
|
||||
- Configurable parse moment, real time, with delay or on focus loss.
|
||||
- Capture node, but use it? Because of generic.
|
||||
- Node must use resolved text as source.
|
||||
- Can't use a binding for this?
|
||||
- How to avoid infinite update loop AND delay update?
|
||||
- When the value var updates there is no delay, only Txt->T has potential delay.
|
||||
|
||||
* Implement `required`.
|
||||
- It must set error, but not from the start.
|
||||
- Some mechanism triggers a validation even if the field was never touched.
|
||||
|
|
|
@ -864,52 +864,29 @@ fn form_editor_window(is_open: ArcVar<bool>) -> WindowRoot {
|
|||
target = "field-version";
|
||||
},
|
||||
{
|
||||
let version = var(Version::default());
|
||||
|
||||
let version_err = var(Txt::from_str(""));
|
||||
let version_ok = version.filter_map_bidi(
|
||||
clmv!(version_err, |ver| {
|
||||
version_err.set(Txt::from_str(""));
|
||||
Some(ver.to_text())
|
||||
}),
|
||||
clmv!(version_err, |txt| {
|
||||
match txt.parse() {
|
||||
Ok(ver) => {
|
||||
version_err.set(Txt::from_str(""));
|
||||
Some(ver)
|
||||
},
|
||||
Err(e) => {
|
||||
version_err.set(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}),
|
||||
|| Version::default().to_text()
|
||||
);
|
||||
|
||||
Stack! {
|
||||
let error = var(Txt::from_static(""));
|
||||
Container! {
|
||||
grid::cell::row = 2;
|
||||
grid::cell::column = 1;
|
||||
|
||||
direction = StackDirection::top_to_bottom();
|
||||
children = ui_vec![
|
||||
TextInput! {
|
||||
id = "field-version";
|
||||
txt = version_ok;
|
||||
child = TextInput! {
|
||||
id = "field-version";
|
||||
txt_parse = var(Version::default());
|
||||
|
||||
when !#{version_err.clone()}.is_empty() {
|
||||
border = {
|
||||
widths: 1,
|
||||
sides: colors::ROSE,
|
||||
};
|
||||
}
|
||||
},
|
||||
Text! {
|
||||
txt = version_err;
|
||||
font_color = colors::ROSE;
|
||||
font_size = 0.8.em();
|
||||
when #has_data_error {
|
||||
border = {
|
||||
widths: 1,
|
||||
sides: colors::ROSE,
|
||||
};
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
get_data_error_txt = error.clone();
|
||||
child_insert_below = Text! {
|
||||
txt = error;
|
||||
font_color = colors::ROSE;
|
||||
font_size = 0.8.em();
|
||||
}, 0;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
@ -954,13 +931,22 @@ impl std::str::FromStr for Version {
|
|||
|
||||
let mut split = s.split('.');
|
||||
if let Some(major) = split.next() {
|
||||
r.major = u32::from_str(major).map_err(|e| e.to_text())?;
|
||||
if !major.is_empty() {
|
||||
r.major = u32::from_str(major).map_err(|e| e.to_text())?;
|
||||
}
|
||||
}
|
||||
if let Some(minor) = split.next() {
|
||||
r.minor = u32::from_str(minor).map_err(|e| e.to_text())?;
|
||||
if !minor.is_empty() {
|
||||
r.minor = u32::from_str(minor).map_err(|e| e.to_text())?;
|
||||
}
|
||||
}
|
||||
if let Some(rev) = split.next() {
|
||||
r.rev = u32::from_str(rev).map_err(|e| e.to_text())?;
|
||||
if !rev.is_empty() {
|
||||
r.rev = u32::from_str(rev).map_err(|e| e.to_text())?;
|
||||
}
|
||||
}
|
||||
if split.next().is_some() {
|
||||
return Err("expected maximum of 3 version numbers".into())
|
||||
}
|
||||
|
||||
Ok(r)
|
||||
|
|
|
@ -341,6 +341,8 @@ pub mod prelude {
|
|||
#[doc(no_inline)]
|
||||
pub use crate::properties::access::{access_role, accessible};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::properties::data_context::{DataNoteHandle, DataNoteLevel};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::properties::events::{self, gesture::*, keyboard::*};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::properties::filters::*;
|
||||
|
|
|
@ -119,7 +119,7 @@ pub fn has_data_error(child: impl UiNode, any: impl IntoVar<bool>) -> impl UiNod
|
|||
///
|
||||
/// This service enables data flow from a context to descendants, a little like an anonymous context var, and
|
||||
/// from descendants up-to contexts.
|
||||
///
|
||||
///
|
||||
/// Arbitrary data can be set on a context using the [`data`] property and retrieved using [`DATA.get`] or [`DATA.req`],
|
||||
/// behaving a little like an anonymous context var. Only one data entry and type can exist in a context, nested
|
||||
/// [`data`] properties override the parent data and type in their context.
|
||||
|
@ -130,7 +130,7 @@ pub fn has_data_error(child: impl UiNode, any: impl IntoVar<bool>) -> impl UiNod
|
|||
/// property gets the error formatted for display. Data notes are aggregated from descendants up-to the context, continuing
|
||||
/// up to outer nested contexts too, this means that you can get data errors for a form field by setting [`get_data_error_txt`] on
|
||||
/// the field widget, and get all form errors from that field and others by also setting [`get_data_error_txt`] in the form widget.
|
||||
///
|
||||
///
|
||||
/// [`data`]: fn@data
|
||||
/// [`get_data_notes`]: fn@get_data_notes
|
||||
/// [`get_data_error_txt`]: fn@get_data_error_txt
|
||||
|
|
|
@ -94,15 +94,56 @@ pub struct Text(
|
|||
#[property(CHILD, capture, default(""), widget_impl(Text))]
|
||||
pub fn txt(txt: impl IntoVar<Txt>) {}
|
||||
|
||||
/// The value that is converted to and from text.
|
||||
/// Value that is parsed from the text and displayed as the text.
|
||||
///
|
||||
/// This is an alternative to [`txt`] that converts to and from `T`. If `T: VarValue + Display + FromStr where FromStr::Err: Display`
|
||||
/// the type is compatible with this property.
|
||||
///
|
||||
/// If the parse operation fails the value variable is not updated and the error display text is set in [`DATA.invalidate`], you
|
||||
/// can use [`has_data_error`] and [`get_data_error_txt`] to display the error.
|
||||
///
|
||||
/// [`txt`]: fn@txt
|
||||
/// [`DATA.invalidate`]: crate::properties::data_context::DATA::invalidate
|
||||
/// [`has_data_error`]: fn@crate::properties::data_context::has_data_error
|
||||
/// [`get_data_error_txt`]: fn@crate::properties::data_context::get_data_error_txt
|
||||
#[property(CHILD, widget_impl(Text))]
|
||||
pub fn txt_parse<T>(child: impl UiNode, value: impl IntoVar<T>) -> impl UiNode
|
||||
where
|
||||
T: VarValue + std::str::FromStr + std::fmt::Display,
|
||||
T: TxtParseValue,
|
||||
{
|
||||
nodes::parse_text(child, value)
|
||||
}
|
||||
|
||||
/// A type that can be a var value, parse and display.
|
||||
///
|
||||
/// This trait is used by [`txt_parse`]. It is implemented for all types that are
|
||||
/// `VarValue + FromStr + Display where FromStr::Err: Display`.
|
||||
///
|
||||
/// [`txt_parse`]: fn@txt_parse
|
||||
pub trait TxtParseValue: VarValue {
|
||||
/// Try parse `Self` from `txt`, formats the error for display.
|
||||
///
|
||||
/// Note that the widget context is not available here as this method is called in the app context.
|
||||
fn from_txt(txt: &Txt) -> Result<Self, Txt>;
|
||||
/// Display the value, the returned text can be parsed back to an equal value.
|
||||
///
|
||||
/// Note that the widget context is not available here as this method is called in the app context.
|
||||
fn to_txt(&self) -> Txt;
|
||||
}
|
||||
impl<T> TxtParseValue for T
|
||||
where
|
||||
T: VarValue + std::str::FromStr + std::fmt::Display,
|
||||
<Self as std::str::FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
fn from_txt(txt: &Txt) -> Result<Self, Txt> {
|
||||
T::from_str(txt).map_err(|e| e.to_text())
|
||||
}
|
||||
|
||||
fn to_txt(&self) -> Txt {
|
||||
self.to_text()
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
fn widget_intrinsic(&mut self) {
|
||||
self.widget_builder().push_build_action(|wgt| {
|
||||
|
|
|
@ -2668,14 +2668,53 @@ fn lines_wrap_counter(txt: &ShapedText) -> impl Iterator<Item = u32> + '_ {
|
|||
|
||||
pub(super) fn parse_text<T>(child: impl UiNode, value: impl IntoVar<T>) -> impl UiNode
|
||||
where
|
||||
T: VarValue + std::str::FromStr + std::fmt::Display,
|
||||
T: super::TxtParseValue,
|
||||
{
|
||||
let value = value.into_var();
|
||||
match_node(child, move |c, op| match op {
|
||||
let error = var(Txt::from_static(""));
|
||||
let mut _error_note = DataNoteHandle::dummy();
|
||||
match_node(child, move |_, op| match op {
|
||||
UiNodeOp::Init => {
|
||||
let _ = PARSE_TXT_VAR.set_from_map(&value, |val| val.to_txt());
|
||||
let binding = PARSE_TXT_VAR.bind_filter_map_bidi(
|
||||
&value,
|
||||
clmv!(error, |txt| match T::from_txt(txt) {
|
||||
Ok(val) => {
|
||||
error.set(Txt::from_static(""));
|
||||
Some(val)
|
||||
}
|
||||
Err(e) => {
|
||||
error.set(e);
|
||||
None
|
||||
}
|
||||
}),
|
||||
clmv!(error, |val| {
|
||||
error.set(Txt::from_static(""));
|
||||
Some(val.to_txt())
|
||||
}),
|
||||
);
|
||||
WIDGET.sub_var(&error).push_var_handles(binding);
|
||||
}
|
||||
UiNodeOp::Deinit => {
|
||||
_error_note = DataNoteHandle::dummy();
|
||||
}
|
||||
UiNodeOp::Update { .. } => {
|
||||
if let Some(error) = error.get_new() {
|
||||
_error_note = if error.is_empty() {
|
||||
DataNoteHandle::dummy()
|
||||
} else {
|
||||
DATA.invalidate(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
|
||||
context_var! {
|
||||
static PARSE_TXT_VAR: Txt = Txt::from_static("");
|
||||
}
|
||||
|
||||
pub(super) fn parse_text_ctx(child: impl UiNode, text: BoxedVar<Txt>) -> impl UiNode {
|
||||
child
|
||||
}
|
||||
with_context_var(child, PARSE_TXT_VAR, text)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue