summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--examples/component/Cargo.toml11
-rw-r--r--examples/component/src/main.rs180
-rw-r--r--lazy/src/component.rs13
4 files changed, 200 insertions, 5 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 39f32a25..f91e4a84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -66,6 +66,7 @@ members = [
"examples/bezier_tool",
"examples/clock",
"examples/color_palette",
+ "examples/component",
"examples/counter",
"examples/custom_widget",
"examples/download_progress",
diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml
new file mode 100644
index 00000000..5761db9f
--- /dev/null
+++ b/examples/component/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "component"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+iced_native = { path = "../../native" }
+iced_lazy = { path = "../../lazy" }
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
new file mode 100644
index 00000000..58d3f354
--- /dev/null
+++ b/examples/component/src/main.rs
@@ -0,0 +1,180 @@
+use iced::{Container, Element, Length, Sandbox, Settings};
+use numeric_input::NumericInput;
+
+pub fn main() -> iced::Result {
+ Component::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Component {
+ numeric_input: numeric_input::State,
+ value: Option<u32>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ NumericInputChanged(Option<u32>),
+}
+
+impl Sandbox for Component {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Component - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::NumericInputChanged(value) => {
+ self.value = value;
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ Container::new(NumericInput::new(
+ &mut self.numeric_input,
+ self.value,
+ Message::NumericInputChanged,
+ ))
+ .padding(20)
+ .height(Length::Fill)
+ .center_y()
+ .into()
+ }
+}
+
+mod numeric_input {
+ use iced_lazy::component::{self, Component};
+ use iced_native::alignment::{self, Alignment};
+ use iced_native::text;
+ use iced_native::widget::button::{self, Button};
+ use iced_native::widget::text_input::{self, TextInput};
+ use iced_native::widget::{Row, Text};
+ use iced_native::{Element, Length};
+
+ pub struct NumericInput<'a, Message> {
+ state: &'a mut State,
+ value: Option<u32>,
+ on_change: Box<dyn Fn(Option<u32>) -> Message>,
+ }
+
+ #[derive(Default)]
+ pub struct State {
+ input: text_input::State,
+ decrement_button: button::State,
+ increment_button: button::State,
+ }
+
+ #[derive(Debug, Clone)]
+ pub enum Event {
+ InputChanged(String),
+ IncrementPressed,
+ DecrementPressed,
+ }
+
+ impl<'a, Message> NumericInput<'a, Message> {
+ pub fn new(
+ state: &'a mut State,
+ value: Option<u32>,
+ on_change: impl Fn(Option<u32>) -> Message + 'static,
+ ) -> Self {
+ Self {
+ state,
+ value,
+ on_change: Box::new(on_change),
+ }
+ }
+ }
+
+ impl<'a, Message, Renderer> Component<Message, Renderer>
+ for NumericInput<'a, Message>
+ where
+ Renderer: 'a + text::Renderer,
+ {
+ type Event = Event;
+
+ fn update(&mut self, event: Event) -> Option<Message> {
+ match event {
+ Event::IncrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_add(1),
+ ))),
+ Event::DecrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_sub(1),
+ ))),
+ Event::InputChanged(value) => {
+ if value.is_empty() {
+ Some((self.on_change)(None))
+ } else {
+ value
+ .parse()
+ .ok()
+ .map(Some)
+ .map(self.on_change.as_ref())
+ }
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Event, Renderer> {
+ let button = |state, label, on_press| {
+ Button::new(
+ state,
+ Text::new(label)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .vertical_alignment(alignment::Vertical::Center),
+ )
+ .width(Length::Units(50))
+ .on_press(on_press)
+ };
+
+ Row::with_children(vec![
+ button(
+ &mut self.state.decrement_button,
+ "-",
+ Event::DecrementPressed,
+ )
+ .into(),
+ TextInput::new(
+ &mut self.state.input,
+ "Type a number",
+ self.value
+ .as_ref()
+ .map(u32::to_string)
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or(""),
+ Event::InputChanged,
+ )
+ .padding(10)
+ .into(),
+ button(
+ &mut self.state.increment_button,
+ "+",
+ Event::IncrementPressed,
+ )
+ .into(),
+ ])
+ .align_items(Alignment::Fill)
+ .spacing(10)
+ .into()
+ }
+ }
+
+ impl<'a, Message, Renderer> From<NumericInput<'a, Message>>
+ for Element<'a, Message, Renderer>
+ where
+ Message: 'a,
+ Renderer: text::Renderer + 'a,
+ {
+ fn from(numeric_input: NumericInput<'a, Message>) -> Self {
+ component::view(Box::new(numeric_input))
+ }
+ }
+}
diff --git a/lazy/src/component.rs b/lazy/src/component.rs
index 3296533e..04832d9d 100644
--- a/lazy/src/component.rs
+++ b/lazy/src/component.rs
@@ -11,10 +11,12 @@ use ouroboros::self_referencing;
use std::marker::PhantomData;
pub fn view<'a, Event, Message, Renderer>(
- component: &'a mut dyn Component<Message, Renderer, Event = Event>,
+ component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>,
) -> Element<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Message: 'a,
+ Event: 'a,
+ Renderer: iced_native::Renderer + 'a,
{
Element::new(Instance {
state: Some(
@@ -43,8 +45,8 @@ struct Instance<'a, Message, Renderer, Event> {
}
#[self_referencing]
-struct State<'a, Message, Renderer, Event> {
- component: &'a mut dyn Component<Message, Renderer, Event = Event>,
+struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> {
+ component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>,
#[borrows(mut component)]
#[covariant]
@@ -106,7 +108,8 @@ where
});
if !local_messages.is_empty() {
- let component = self.state.take().unwrap().into_heads().component;
+ let mut component =
+ self.state.take().unwrap().into_heads().component;
messages.extend(
local_messages