From afd9274de26ccf65285df02007b4ddb697bea9a3 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Sat, 18 Apr 2020 14:42:48 +0200
Subject: Draft `ComboBox` and `Menu` layer

---
 native/src/element.rs           |  25 +++-
 native/src/layer.rs             |   5 +
 native/src/layer/menu.rs        | 305 ++++++++++++++++++++++++++++++++++++++++
 native/src/lib.rs               |   4 +-
 native/src/overlay.rs           | 100 ++++++++++++-
 native/src/user_interface.rs    |  16 +--
 native/src/widget.rs            |   3 +
 native/src/widget/combo_box.rs  | 256 +++++++++++++++++++++++++++++++++
 native/src/widget/scrollable.rs |   6 +-
 9 files changed, 698 insertions(+), 22 deletions(-)
 create mode 100644 native/src/layer/menu.rs
 create mode 100644 native/src/widget/combo_box.rs

(limited to 'native')

diff --git a/native/src/element.rs b/native/src/element.rs
index 01379d2d..c881871a 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -2,6 +2,7 @@ use crate::{
     layout, Clipboard, Color, Event, Hasher, Layout, Length, Overlay, Point,
     Widget,
 };
+use std::rc::Rc;
 
 /// A generic [`Widget`].
 ///
@@ -282,7 +283,7 @@ where
 
 struct Map<'a, A, B, Renderer> {
     widget: Box<dyn Widget<'a, A, Renderer> + 'a>,
-    mapper: Box<dyn Fn(A) -> B>,
+    mapper: Rc<dyn Fn(A) -> B>,
 }
 
 impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
@@ -295,14 +296,16 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
     {
         Map {
             widget,
-            mapper: Box::new(mapper),
+            mapper: Rc::new(mapper),
         }
     }
 }
 
 impl<'a, A, B, Renderer> Widget<'a, B, Renderer> for Map<'a, A, B, Renderer>
 where
-    Renderer: crate::Renderer,
+    Renderer: crate::Renderer + 'a,
+    A: 'static,
+    B: 'static,
 {
     fn width(&self) -> Length {
         self.widget.width()
@@ -359,6 +362,15 @@ where
     fn hash_layout(&self, state: &mut Hasher) {
         self.widget.hash_layout(state);
     }
+
+    fn overlay(
+        &mut self,
+        layout: Layout<'_>,
+    ) -> Option<Overlay<'a, B, Renderer>> {
+        self.widget
+            .overlay(layout)
+            .map(|overlay| overlay.map(self.mapper.clone()))
+    }
 }
 
 struct Explain<'a, Message, Renderer: crate::Renderer> {
@@ -434,4 +446,11 @@ where
     fn hash_layout(&self, state: &mut Hasher) {
         self.element.widget.hash_layout(state);
     }
+
+    fn overlay(
+        &mut self,
+        layout: Layout<'_>,
+    ) -> Option<Overlay<'a, Message, Renderer>> {
+        self.element.overlay(layout)
+    }
 }
diff --git a/native/src/layer.rs b/native/src/layer.rs
index d89fb4e5..eacfe94b 100644
--- a/native/src/layer.rs
+++ b/native/src/layer.rs
@@ -1,3 +1,8 @@
+pub mod menu;
+
+#[doc(no_inline)]
+pub use menu::Menu;
+
 use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size};
 
 pub trait Layer<Message, Renderer>
diff --git a/native/src/layer/menu.rs b/native/src/layer/menu.rs
new file mode 100644
index 00000000..9e26767b
--- /dev/null
+++ b/native/src/layer/menu.rs
@@ -0,0 +1,305 @@
+use crate::{
+    container, layout, mouse, scrollable, Clipboard, Container, Element, Event,
+    Hasher, Layer, Layout, Length, Point, Rectangle, Scrollable, Size, Widget,
+};
+use std::borrow::Cow;
+
+pub struct Menu<'a, Message, Renderer: self::Renderer> {
+    container: Container<'a, Message, Renderer>,
+    is_open: &'a mut bool,
+    width: u16,
+}
+
+#[derive(Default)]
+pub struct State {
+    scrollable: scrollable::State,
+    hovered_option: Option<usize>,
+    is_open: bool,
+}
+
+impl State {
+    pub fn is_open(&self) -> bool {
+        self.is_open
+    }
+
+    pub fn open(&mut self, hovered_option: Option<usize>) {
+        self.is_open = true;
+        self.hovered_option = hovered_option;
+    }
+}
+
+impl<'a, Message, Renderer: self::Renderer> Menu<'a, Message, Renderer>
+where
+    Message: 'static,
+    Renderer: 'a,
+{
+    pub fn new<T: 'a>(
+        state: &'a mut State,
+        options: impl Into<Cow<'a, [T]>>,
+        on_selected: Box<dyn Fn(T) -> Message>,
+        width: u16,
+        text_size: u16,
+        padding: u16,
+    ) -> Self
+    where
+        T: Clone + ToString,
+        [T]: ToOwned<Owned = Vec<T>>,
+    {
+        let container = Container::new(
+            Scrollable::new(&mut state.scrollable).push(List::new(
+                &mut state.hovered_option,
+                options,
+                on_selected,
+                text_size,
+                padding,
+            )),
+        )
+        .padding(1);
+
+        Self {
+            container,
+            is_open: &mut state.is_open,
+            width,
+        }
+    }
+}
+
+impl<'a, Message, Renderer> Layer<Message, Renderer>
+    for Menu<'a, Message, Renderer>
+where
+    Renderer: self::Renderer,
+{
+    fn layout(
+        &self,
+        renderer: &Renderer,
+        bounds: Size,
+        position: Point,
+    ) -> layout::Node {
+        let limits = layout::Limits::new(
+            Size::ZERO,
+            Size::new(bounds.width - position.x, bounds.height - position.y),
+        )
+        .width(Length::Units(self.width));
+
+        let mut node = self.container.layout(renderer, &limits);
+
+        node.move_to(position);
+
+        node
+    }
+
+    fn hash_layout(&self, state: &mut Hasher, position: Point) {
+        use std::hash::Hash;
+
+        (position.x as u32).hash(state);
+        (position.y as u32).hash(state);
+    }
+
+    fn on_event(
+        &mut self,
+        event: Event,
+        layout: Layout<'_>,
+        cursor_position: Point,
+        messages: &mut Vec<Message>,
+        renderer: &Renderer,
+        clipboard: Option<&dyn Clipboard>,
+    ) {
+        let bounds = layout.bounds();
+        let current_messages = messages.len();
+
+        self.container.on_event(
+            event.clone(),
+            layout,
+            cursor_position,
+            messages,
+            renderer,
+            clipboard,
+        );
+
+        match event {
+            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+                if !bounds.contains(cursor_position)
+                    || current_messages < messages.len() =>
+            {
+                *self.is_open = false;
+            }
+            _ => {}
+        }
+    }
+
+    fn draw(
+        &self,
+        renderer: &mut Renderer,
+        defaults: &Renderer::Defaults,
+        layout: Layout<'_>,
+        cursor_position: Point,
+    ) -> Renderer::Output {
+        let primitives =
+            self.container
+                .draw(renderer, defaults, layout, cursor_position);
+
+        renderer.decorate(layout.bounds(), cursor_position, primitives)
+    }
+}
+
+struct List<'a, T, Message>
+where
+    [T]: ToOwned,
+{
+    hovered_option: &'a mut Option<usize>,
+    options: Cow<'a, [T]>,
+    on_selected: Box<dyn Fn(T) -> Message>,
+    text_size: u16,
+    padding: u16,
+}
+
+impl<'a, T, Message> List<'a, T, Message>
+where
+    [T]: ToOwned,
+{
+    pub fn new(
+        hovered_option: &'a mut Option<usize>,
+        options: impl Into<Cow<'a, [T]>>,
+        on_selected: Box<dyn Fn(T) -> Message>,
+        text_size: u16,
+        padding: u16,
+    ) -> Self {
+        List {
+            hovered_option,
+            options: options.into(),
+            on_selected,
+            text_size,
+            padding,
+        }
+    }
+}
+
+impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer>
+    for List<'a, T, Message>
+where
+    T: ToString + Clone,
+    [T]: ToOwned,
+    Renderer: self::Renderer,
+{
+    fn width(&self) -> Length {
+        Length::Fill
+    }
+
+    fn height(&self) -> Length {
+        Length::Shrink
+    }
+
+    fn layout(
+        &self,
+        _renderer: &Renderer,
+        limits: &layout::Limits,
+    ) -> layout::Node {
+        use std::f32;
+
+        let limits = limits.width(Length::Fill).height(Length::Shrink);
+
+        let size = {
+            let intrinsic = Size::new(
+                0.0,
+                f32::from(self.text_size + self.padding * 2)
+                    * self.options.len() as f32,
+            );
+
+            limits.resolve(intrinsic)
+        };
+
+        layout::Node::new(size)
+    }
+
+    fn hash_layout(&self, state: &mut Hasher) {
+        use std::hash::Hash as _;
+
+        0.hash(state);
+    }
+
+    fn on_event(
+        &mut self,
+        event: Event,
+        layout: Layout<'_>,
+        cursor_position: Point,
+        messages: &mut Vec<Message>,
+        _renderer: &Renderer,
+        _clipboard: Option<&dyn Clipboard>,
+    ) {
+        match event {
+            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+                let bounds = layout.bounds();
+
+                if bounds.contains(cursor_position) {
+                    if let Some(index) = *self.hovered_option {
+                        if let Some(option) = self.options.get(index) {
+                            messages.push((self.on_selected)(option.clone()));
+                        }
+                    }
+                }
+            }
+            Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+                let bounds = layout.bounds();
+
+                if bounds.contains(cursor_position) {
+                    *self.hovered_option = Some(
+                        ((cursor_position.y - bounds.y)
+                            / f32::from(self.text_size + self.padding * 2))
+                            as usize,
+                    );
+                }
+            }
+            _ => {}
+        }
+    }
+
+    fn draw(
+        &self,
+        renderer: &mut Renderer,
+        _defaults: &Renderer::Defaults,
+        layout: Layout<'_>,
+        cursor_position: Point,
+    ) -> Renderer::Output {
+        self::Renderer::draw(
+            renderer,
+            layout.bounds(),
+            cursor_position,
+            &self.options,
+            *self.hovered_option,
+            self.text_size,
+            self.padding,
+        )
+    }
+}
+
+pub trait Renderer: scrollable::Renderer + container::Renderer {
+    fn decorate(
+        &mut self,
+        bounds: Rectangle,
+        cursor_position: Point,
+        primitive: Self::Output,
+    ) -> Self::Output;
+
+    fn draw<T: ToString>(
+        &mut self,
+        bounds: Rectangle,
+        cursor_position: Point,
+        options: &[T],
+        hovered_option: Option<usize>,
+        text_size: u16,
+        padding: u16,
+    ) -> Self::Output;
+}
+
+impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
+    for List<'a, T, Message>
+where
+    T: ToString + Clone,
+    [T]: ToOwned,
+    Message: 'static,
+    Renderer: self::Renderer,
+{
+    fn into(self) -> Element<'a, Message, Renderer> {
+        Element::new(self)
+    }
+}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 6974c2bd..99d80126 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -31,11 +31,12 @@
 //! [`UserInterface`]: struct.UserInterface.html
 //! [renderer]: renderer/index.html
 //#![deny(missing_docs)]
-#![deny(missing_debug_implementations)]
+//#![deny(missing_debug_implementations)]
 #![deny(unused_results)]
 #![forbid(unsafe_code)]
 #![forbid(rust_2018_idioms)]
 pub mod keyboard;
+pub mod layer;
 pub mod layout;
 pub mod mouse;
 pub mod program;
@@ -48,7 +49,6 @@ mod clipboard;
 mod element;
 mod event;
 mod hasher;
-mod layer;
 mod overlay;
 mod runtime;
 mod user_interface;
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index a4bd5ea3..d7a1e082 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -1,4 +1,5 @@
 use crate::{layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size};
+use std::rc::Rc;
 
 #[allow(missing_debug_implementations)]
 pub struct Overlay<'a, Message, Renderer> {
@@ -17,6 +18,18 @@ where
         Self { position, layer }
     }
 
+    pub fn map<B>(self, f: Rc<dyn Fn(Message) -> B>) -> Overlay<'a, B, Renderer>
+    where
+        Message: 'static,
+        Renderer: 'a,
+        B: 'static,
+    {
+        Overlay {
+            position: self.position,
+            layer: Box::new(Map::new(self.layer, f)),
+        }
+    }
+
     pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node {
         self.layer.layout(renderer, bounds, self.position)
     }
@@ -37,12 +50,87 @@ where
 
     pub fn on_event(
         &mut self,
-        _event: Event,
-        _layout: Layout<'_>,
-        _cursor_position: Point,
-        _messages: &mut Vec<Message>,
-        _renderer: &Renderer,
-        _clipboard: Option<&dyn Clipboard>,
+        event: Event,
+        layout: Layout<'_>,
+        cursor_position: Point,
+        messages: &mut Vec<Message>,
+        renderer: &Renderer,
+        clipboard: Option<&dyn Clipboard>,
     ) {
+        self.layer.on_event(
+            event,
+            layout,
+            cursor_position,
+            messages,
+            renderer,
+            clipboard,
+        )
+    }
+}
+
+struct Map<'a, A, B, Renderer> {
+    layer: Box<dyn Layer<A, Renderer> + 'a>,
+    mapper: Rc<dyn Fn(A) -> B>,
+}
+
+impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
+    pub fn new(
+        layer: Box<dyn Layer<A, Renderer> + 'a>,
+        mapper: Rc<dyn Fn(A) -> B + 'static>,
+    ) -> Map<'a, A, B, Renderer> {
+        Map { layer, mapper }
+    }
+}
+
+impl<'a, A, B, Renderer> Layer<B, Renderer> for Map<'a, A, B, Renderer>
+where
+    Renderer: crate::Renderer,
+{
+    fn layout(
+        &self,
+        renderer: &Renderer,
+        bounds: Size,
+        position: Point,
+    ) -> layout::Node {
+        self.layer.layout(renderer, bounds, position)
+    }
+
+    fn on_event(
+        &mut self,
+        event: Event,
+        layout: Layout<'_>,
+        cursor_position: Point,
+        messages: &mut Vec<B>,
+        renderer: &Renderer,
+        clipboard: Option<&dyn Clipboard>,
+    ) {
+        let mut original_messages = Vec::new();
+
+        self.layer.on_event(
+            event,
+            layout,
+            cursor_position,
+            &mut original_messages,
+            renderer,
+            clipboard,
+        );
+
+        original_messages
+            .drain(..)
+            .for_each(|message| messages.push((self.mapper)(message)));
+    }
+
+    fn draw(
+        &self,
+        renderer: &mut Renderer,
+        defaults: &Renderer::Defaults,
+        layout: Layout<'_>,
+        cursor_position: Point,
+    ) -> Renderer::Output {
+        self.layer.draw(renderer, defaults, layout, cursor_position)
+    }
+
+    fn hash_layout(&self, state: &mut Hasher, position: Point) {
+        self.layer.hash_layout(state, position);
     }
 }
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 6758bce3..12cea684 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -217,6 +217,14 @@ where
 
         for event in events {
             if let Some(overlay) = &mut self.overlay {
+                let base_cursor =
+                    if overlay.layout.bounds().contains(cursor_position) {
+                        // TODO: Encode cursor availability
+                        Point::new(-1.0, -1.0)
+                    } else {
+                        cursor_position
+                    };
+
                 overlay.root.on_event(
                     event.clone(),
                     Layout::new(&overlay.layout),
@@ -226,14 +234,6 @@ where
                     clipboard,
                 );
 
-                let base_cursor =
-                    if overlay.layout.bounds().contains(cursor_position) {
-                        // TODO: Encode cursor availability
-                        Point::new(-1.0, -1.0)
-                    } else {
-                        cursor_position
-                    };
-
                 self.base.root.widget.on_event(
                     event,
                     Layout::new(&self.base.layout),
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 0494636f..664a0cfd 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -23,6 +23,7 @@
 pub mod button;
 pub mod checkbox;
 pub mod column;
+pub mod combo_box;
 pub mod container;
 pub mod image;
 pub mod pane_grid;
@@ -43,6 +44,8 @@ pub use checkbox::Checkbox;
 #[doc(no_inline)]
 pub use column::Column;
 #[doc(no_inline)]
+pub use combo_box::ComboBox;
+#[doc(no_inline)]
 pub use container::Container;
 #[doc(no_inline)]
 pub use image::Image;
diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs
new file mode 100644
index 00000000..0b25b836
--- /dev/null
+++ b/native/src/widget/combo_box.rs
@@ -0,0 +1,256 @@
+use crate::{
+    layer::{self, menu},
+    layout, mouse, scrollable, text, Clipboard, Element, Event, Hasher, Layout,
+    Length, Overlay, Point, Rectangle, Size, Vector, Widget,
+};
+use std::borrow::Cow;
+
+pub struct ComboBox<'a, T, Message>
+where
+    [T]: ToOwned<Owned = Vec<T>>,
+{
+    internal: Option<Internal<'a, T, Message>>,
+    options: Cow<'a, [T]>,
+    selected: Option<T>,
+    width: Length,
+    padding: u16,
+    text_size: Option<u16>,
+}
+
+#[derive(Default)]
+pub struct State {
+    menu: menu::State,
+}
+
+pub struct Internal<'a, T, Message> {
+    menu: &'a mut menu::State,
+    on_selected: Box<dyn Fn(T) -> Message>,
+}
+
+impl<'a, T: 'a, Message> ComboBox<'a, T, Message>
+where
+    T: ToString,
+    [T]: ToOwned<Owned = Vec<T>>,
+{
+    pub fn new(
+        state: &'a mut State,
+        options: impl Into<Cow<'a, [T]>>,
+        selected: Option<T>,
+        on_selected: impl Fn(T) -> Message + 'static,
+    ) -> Self {
+        Self {
+            internal: Some(Internal {
+                menu: &mut state.menu,
+                on_selected: Box::new(on_selected),
+            }),
+            options: options.into(),
+            selected,
+            width: Length::Shrink,
+            text_size: None,
+            padding: 5,
+        }
+    }
+
+    /// Sets the width of the [`ComboBox`].
+    ///
+    /// [`ComboBox`]: struct.Button.html
+    pub fn width(mut self, width: Length) -> Self {
+        self.width = width;
+        self
+    }
+
+    /// Sets the padding of the [`ComboBox`].
+    ///
+    /// [`ComboBox`]: struct.Button.html
+    pub fn padding(mut self, padding: u16) -> Self {
+        self.padding = padding;
+        self
+    }
+
+    pub fn text_size(mut self, size: u16) -> Self {
+        self.text_size = Some(size);
+        self
+    }
+}
+
+impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer>
+    for ComboBox<'a, T, Message>
+where
+    T: Clone + ToString + Eq,
+    [T]: ToOwned<Owned = Vec<T>>,
+    Message: 'static,
+    Renderer: self::Renderer + scrollable::Renderer + 'a,
+{
+    fn width(&self) -> Length {
+        Length::Shrink
+    }
+
+    fn height(&self) -> Length {
+        Length::Shrink
+    }
+
+    fn layout(
+        &self,
+        renderer: &Renderer,
+        limits: &layout::Limits,
+    ) -> layout::Node {
+        use std::f32;
+
+        let limits = limits
+            .width(self.width)
+            .height(Length::Shrink)
+            .pad(f32::from(self.padding));
+
+        let text_size = self.text_size.unwrap_or(renderer.default_size());
+
+        let max_width = match self.width {
+            Length::Shrink => {
+                let labels = self.options.iter().map(ToString::to_string);
+
+                labels
+                    .map(|label| {
+                        let (width, _) = renderer.measure(
+                            &label,
+                            text_size,
+                            Renderer::Font::default(),
+                            Size::new(f32::INFINITY, f32::INFINITY),
+                        );
+
+                        width.round() as u32
+                    })
+                    .max()
+                    .unwrap_or(100)
+            }
+            _ => 0,
+        };
+
+        let size = {
+            let intrinsic = Size::new(
+                max_width as f32 + f32::from(text_size),
+                f32::from(text_size),
+            );
+
+            limits.resolve(intrinsic).pad(f32::from(self.padding))
+        };
+
+        layout::Node::new(size)
+    }
+
+    fn hash_layout(&self, state: &mut Hasher) {
+        use std::hash::Hash as _;
+
+        match self.width {
+            Length::Shrink => {
+                self.options
+                    .iter()
+                    .map(ToString::to_string)
+                    .for_each(|label| label.hash(state));
+            }
+            _ => {
+                self.width.hash(state);
+            }
+        }
+    }
+
+    fn on_event(
+        &mut self,
+        event: Event,
+        layout: Layout<'_>,
+        cursor_position: Point,
+        _messages: &mut Vec<Message>,
+        _renderer: &Renderer,
+        _clipboard: Option<&dyn Clipboard>,
+    ) {
+        match event {
+            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+                if let Some(internal) = &mut self.internal {
+                    if layout.bounds().contains(cursor_position) {
+                        let selected = self.selected.as_ref();
+
+                        internal.menu.open(
+                            self.options
+                                .iter()
+                                .position(|option| Some(option) == selected),
+                        );
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+
+    fn draw(
+        &self,
+        renderer: &mut Renderer,
+        _defaults: &Renderer::Defaults,
+        layout: Layout<'_>,
+        cursor_position: Point,
+    ) -> Renderer::Output {
+        self::Renderer::draw(
+            renderer,
+            layout.bounds(),
+            cursor_position,
+            self.selected.as_ref().map(ToString::to_string),
+            self.text_size.unwrap_or(renderer.default_size()),
+            self.padding,
+        )
+    }
+
+    fn overlay(
+        &mut self,
+        layout: Layout<'_>,
+    ) -> Option<Overlay<'a, Message, Renderer>> {
+        let is_open = self
+            .internal
+            .as_ref()
+            .map(|internal| internal.menu.is_open())
+            .unwrap_or(false);
+
+        if is_open {
+            if let Some(Internal { menu, on_selected }) = self.internal.take() {
+                Some(Overlay::new(
+                    layout.position()
+                        + Vector::new(0.0, layout.bounds().height),
+                    Box::new(layer::Menu::new(
+                        menu,
+                        self.options.clone(),
+                        on_selected,
+                        layout.bounds().width.round() as u16,
+                        self.text_size.unwrap_or(20),
+                        self.padding,
+                    )),
+                ))
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    }
+}
+
+pub trait Renderer: text::Renderer + menu::Renderer {
+    const DEFAULT_PADDING: u16;
+
+    fn draw(
+        &mut self,
+        bounds: Rectangle,
+        cursor_position: Point,
+        selected: Option<String>,
+        text_size: u16,
+        padding: u16,
+    ) -> Self::Output;
+}
+
+impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
+    for ComboBox<'a, T, Message>
+where
+    T: Clone + ToString + Eq,
+    [T]: ToOwned<Owned = Vec<T>>,
+    Renderer: self::Renderer + 'a,
+    Message: 'static,
+{
+    fn into(self) -> Element<'a, Message, Renderer> {
+        Element::new(self)
+    }
+}
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index d7ad98e6..25fd8982 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -113,7 +113,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
 impl<'a, Message, Renderer> Widget<'a, Message, Renderer>
     for Scrollable<'a, Message, Renderer>
 where
-    Renderer: self::Renderer + column::Renderer,
+    Renderer: self::Renderer,
 {
     fn width(&self) -> Length {
         Widget::<Message, Renderer>::width(&self.content)
@@ -454,7 +454,7 @@ pub struct Scroller {
 ///
 /// [`Scrollable`]: struct.Scrollable.html
 /// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer + Sized {
+pub trait Renderer: column::Renderer + Sized {
     /// The style supported by this renderer.
     type Style: Default;
 
@@ -502,7 +502,7 @@ pub trait Renderer: crate::Renderer + Sized {
 impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
     for Element<'a, Message, Renderer>
 where
-    Renderer: 'a + self::Renderer + column::Renderer,
+    Renderer: 'a + self::Renderer,
     Message: 'a,
 {
     fn from(
-- 
cgit