Input validation and errors

What’s the correct way of showing an error on a widget? I was thinking about something like the following:

/// Just an example style, I still need to choose a color palette.
const ACTIVE: Style = Style {
    background: iced::Background::Color(Color::WHITE),
    border: Border {
        color: Color::BLACK,
        width: 1.0,
        radius: Radius {
            top_left: 0.,
            top_right: 0.,
            bottom_right: 0.,
            bottom_left: 0.,
        },
    },
    icon: Color::BLACK,
    placeholder: Color::BLACK,
    value: Color::BLACK,
    selection: Color::BLACK,
};

/// This helper function is used in the `view` method, everywhere I need a text input with this particular style.
pub fn custom_text_input<'a, Message>(
    placeholder: &str,
    value: &str,
    is_error: bool,
) -> TextInput<'a, Message>
where
    Message: 'a + Clone,
{
    text_input(placeholder, value).style(|_, status| match status {
        text_input::Status::Active => {
          if is_error {
            ACTIVE
          else {
            // I was thinking to duplicate the ACTIVE style, where the color of the border is red, for example.
            ACTIVE_ERROR
         },
        text_input::Status::Hovered => todo!(),
        text_input::Status::Focused => todo!(),
        text_input::Status::Disabled => todo!(),
    })
}

Is this the intended approach? What is everyone else using?

Thanks in advance

From a conversation on Discord a while back in case this helps:

use iced::widget::{column, container, text, text_input};
use iced::Length::Fill;
use iced::{Element, Size, Task};

fn main() -> iced::Result {
    iced::application("iced • validator example", App::update, App::view)
        .window_size(Size::new(400.0, 200.0))
        .centered()
        .run()
}

#[derive(Debug, Clone)]
enum Message {
    Input(String),
    Submit,
}

#[derive(Default)]
struct App {
    text_input: String,
    last_valid_submission: Option<String>,
}

impl App {
    fn update(&mut self, message: Message) -> Task<Message> {
        match message {
            Message::Input(input) => self.text_input = input,
            Message::Submit => {
                self.last_valid_submission = Some(self.text_input.clone());
                self.text_input.clear();
            }
        }
        Task::none()
    }

    fn view(&self) -> Element<Message> {
        container(
            column![
                input_validator(
                    &self.text_input,
                    is_three_digits,
                    Message::Input,
                    Message::Submit
                ),
                match &self.last_valid_submission {
                    Some(valid) => text!("Last valid submission: {}", valid),
                    None => text("No valid submission yet"),
                }
            ]
            .spacing(20)
            .padding(20),
        )
        .center(Fill)
        .into()
    }
}

fn is_three_digits(input: &str) -> bool {
    input.chars().all(|c| c.is_ascii_digit()) && input.len() == 3
}

fn input_validator<'a, Message: Clone + 'a>(
    input: &'a str,
    validator: impl Fn(&str) -> bool,
    on_input: impl Fn(String) -> Message + 'a,
    on_submit: Message,
) -> Element<'a, Message> {
    let input_valid = validator(&input);

    column![
        text_input("Enter 3 Digits", input)
            .on_input(on_input)
            .on_submit_maybe(input_valid.then_some(on_submit))
            .style(move |theme: &iced::Theme, status| if input.is_empty() {
                text_input::default(theme, status)
            } else {
                text_input::Style {
                    border: iced::Border {
                        color: if input_valid {
                            theme.extended_palette().success.base.color
                        } else {
                            theme.extended_palette().danger.base.color
                        },
                        radius: 0.0.into(),
                        width: 1.0,
                    },
                    ..text_input::default(theme, status)
                }
            })
            .padding(10),
        if input.is_empty() {
            "".into()
        } else if input_valid {
            text("Input is valid! Press <Enter> to submit.").style(text::success)
        } else {
            text("Input must have exactly 3 digits!").style(text::danger)
        },
    ]
    .spacing(10)
    .into()
}
1 Like