iced-rs / iced

A cross-platform GUI library for Rust, inspired by Elm
https://iced.rs
MIT License
23.33k stars 1.07k forks source link

Unable to Use style(..) method in text_input with latest Iced component API #2451

Closed deepakjacob closed 1 month ago

deepakjacob commented 1 month ago

Is there an existing issue for this?

Is this issue related to iced?

What happened?

I have a component like the following. Note the from in the end, i.e., I am using the newest Iced API available in the master branch. I am not able to use the style(..) method for the text_input as the view method won't allow it. This is an "iced" component. The code is as follows:


use iced::{
    widget::{component, row, text_input, Component},
    Element, Renderer,
};

use url::Url;

use crate::styles::custom_style;

pub struct UrlInput<Message> {
    url: String,
    on_input: Box<dyn Fn(String, bool) -> Message>,
}

pub fn url_input<Message>(
    url: String,
    on_input: impl Fn(String, bool) -> Message + 'static,
) -> UrlInput<Message> {
    UrlInput::new(url, on_input)
}

impl<Message> UrlInput<Message> {
    fn new(url: String, on_input: impl Fn(String, bool) -> Message + 'static) -> UrlInput<Message> {
        UrlInput {
            url,
            on_input: Box::new(on_input),
        }
    }
}

#[derive(Debug, Clone)]
pub enum UrlInputEvent {
    UrlChanged(String),
}

#[derive(Debug, Clone)]
pub enum UrlInputState {
    Valid,
    Invalid,
}

impl Default for UrlInputState {
    fn default() -> Self {
        Self::Invalid
    }
}

impl<Message, Theme> Component<Message, Theme> for UrlInput<Message>
where
    Theme: text_input::Catalog + 'static,
    Renderer: iced::advanced::Renderer,
{
    type State = UrlInputState;

    type Event = UrlInputEvent;

    fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Theme, Renderer> {
        let input = text_input("Name", &self.url).style(custom_style);
        row![input].padding(5).spacing(10).into()
    }

    fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            UrlInputEvent::UrlChanged(str) => {
                self.url = str.clone();
                Some((self.on_input)(
                    str.clone(),
                    Url::parse(&str.as_str()).is_ok(),
                ))
            }
        }
    }
}

impl<'a, Message, Theme> From<UrlInput<Message>> for Element<'a, Message, Theme>
where
    Theme: text_input::Catalog + 'static,
    Message: 'a,
{
    fn from(url_input: UrlInput<Message>) -> Self {
        component(url_input)
    }
}

The custom style function is as follows:

use iced::{Background, Border, Color, Theme};
pub fn custom_style(
    theme: &Theme,
    _status: iced::widget::text_input::Status,
) -> iced::widget::text_input::Style {
    let danger = theme.palette().danger;
    let active = iced::widget::text_input::Style {
        background: Background::Color(Color::from_rgb(0.2, 0.2, 0.2)),
        border: Border {
            radius: 5.0.into(),
            width: 2.0,
            color: danger,
        },
        icon: Color::from_rgb(0.7, 0.7, 0.7),
        placeholder: Color::from_rgb(0.5, 0.5, 0.5),
        value: Color::from_rgb(1.0, 1.0, 1.0),
        selection: Color::from_rgb(0.3, 0.3, 0.7),
    };
    active
}

The url_input is used in the Application as follows:


mod styles;
mod url_input;

use iced::Command;
use iced::{widget::column, Alignment, Element, Renderer, Theme};
use uuid::Uuid;
use crate::url_input::url_input;

fn main() -> iced::Result {
    iced::run("Component - Iced", App::update, App::view)
}

#[derive(Default)]
struct App {
    url: String,
    valid_url: bool,
    headers: Vec<PairModel>,
}

#[derive(Debug, Clone)]
enum AppMessage {
    UrlChanged(String, bool),
}

impl App {
    fn update<Message>(&mut self, message: AppMessage) -> Command<Message> {
        match message {
            AppMessage::UrlChanged(url, valid_url) => {
                self.url = url;
                self.valid_url = valid_url;
                Command::none()
            }
        }
    }

    fn view(&self) -> Element<AppMessage, Theme, Renderer> {
        column![
            url_input(self.url.clone(), AppMessage::UrlChanged),
        ]
        // .spacing(10)
        .align_items(Alignment::Start)
        .into()
    }
}

What is the expected behavior?

Should be able to call style(..) without any issue when component api is used.

Version

master

Operating System

Linux

Do you have any log output?

error[E0277]: the trait bound `iced::advanced::iced_graphics::iced_core::Element<'_, UrlInputEvent, Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>>: From<Row<'_, _, Theme, _>>` is not satisfied
  --> src/url_input.rs:59:44
   |
59 |         row![input].padding(5).spacing(10).into()
   |                                            ^^^^ the trait `From<Row<'_, _, Theme, _>>` is not implemented for `Element<'_, UrlInputEvent, Theme, Renderer<Renderer, Renderer>>`, which is required by `Row<'_, _, Theme, _>: Into<_>`
   |
   = help: the following other types implement trait `From<T>`:
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>> as From<PairInput<Message>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>> as From<PairModifyDeleteControl<Message>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, Renderer> as From<iced::widget::Column<'a, Message, Theme, Renderer>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>> as From<UrlInput<Message>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, Renderer> as From<MouseArea<'a, Message, Theme, Renderer>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, Renderer> as From<Row<'a, Message, Theme, Renderer>>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, Renderer> as From<iced::widget::Space>>
             <iced::advanced::iced_graphics::iced_core::Element<'a, Message, Theme, Renderer> as From<iced::widget::Stack<'a, Message, Theme, Renderer>>>
           and 25 others
   = note: required for `Row<'_, _, Theme, _>` to implement `Into<iced::advanced::iced_graphics::iced_core::Element<'_, UrlInputEvent, Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>>>`
help: consider removing this method call, as the receiver has type `Row<'_, _, Theme, _>` and `Row<'_, _, Theme, _>: From<Row<'_, _, Theme, _>>` trivially holds
   |
59 -         row![input].padding(5).spacing(10).into()
59 +         row![input].padding(5).into()
   |
deepakjacob commented 1 month ago

Any help or suggestions would be greatly appreciated. Thank you!

alex-ds13 commented 1 month ago

The problem here is that you are building your component with a generic theme but then you are using a style function with the builtin iced::Theme. You could probably make your style function assume the theme to be generic as well and then make sure that the generic theme as text_input::Catalog's Class implements From text_input::StyleFn. But that is really hard and probably completely unnecessary (also there would be no way to use the palette function then).

So the simpler way to fix this is to just use the builtin theme everywhere.

Change this line:

impl<Message, Theme> Component<Message, Theme> for UrlInput<Message>
where
    Theme: text_input::Catalog + 'static,
    Renderer: iced::advanced::Renderer,

to

impl<Message> Component<Message, iced::Theme> for UrlInput<Message>
where
    Renderer: iced::advanced::Renderer,

Change this:

fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Theme, Renderer> {

to

fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, iced::Theme, Renderer> {

And lastly change this:

impl<'a, Message, Theme> From<UrlInput<Message>> for Element<'a, Message, Theme>
where
    Theme: text_input::Catalog + 'static,
    Message: 'a,
{

to

impl<'a, Message> From<UrlInput<Message>> for Element<'a, Message, iced::Theme>
where
    Message: 'a,
{

This should fix it.

deepakjacob commented 1 month ago

That worked perfectly. Thank you @alex-ds13 for taking the time to provide the solution.