Component

Sometimes you want to create your own reusable custom components that you can reuse through your applications. That is where the Component trait comes in place. You can turn anything that implements this trait easily into an Element with the component function.

Components should only be used as reusable widgets and not for organizing code or splitting your applications into different parts!

Note: Component is only available on crate feature lazy.

The 3 parts of a Component

Each component is build out of three parts: the component itself, the state of the component and the internal message of the component.

These are similar to the 3 parts of your application with one difference. The internal state that can change based on events is represented as an extra a struct, not as the component struct itself.

Component itself

At first, we create the component struct itself:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

As you see, it has one field link. Here, we store the link that will be displayed and opened when the hyperlink is clicked.

Message / Event

Now we need to create the message that will be used inside our component:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Here we have three events for our component. The Clicked event is called every time the user clicks onto the component. The MouseEnter and MouseExit events are called when the mouse enters over the component and leaves (in other words, hovering over the component).

State

In the state of our component, we store if the mouse hovers over the component.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Implementing the Component trait

Now we can implement the Component trait for the Hyperlink struct.

Full Implementation

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Types

We define the types for our state and message/event:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

View and Update logic

Every time an event is called, the update and view function gets called.

In the update function, we set the hovered field of the state or print "open link". Instead of printing something you could use crates like opener to open files and website, but that is beyond this example.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

As you see, we return None in the update function. Instead of None we could return a Some(Message) that is propagated to the parent application.

We define in the view function how our component looks on the screen. In this case, we have a mouse area with a text inside.

The text color changes when the mouse is hovered over the component. If the mouse hovers above the component is determined by the state.hovered field that is hold up to date by our update function.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}