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.
Creating a Hyperlink Example
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()
}
}