diff options
| author | 2019-09-24 15:39:33 +0200 | |
|---|---|---|
| committer | 2019-09-24 15:39:33 +0200 | |
| commit | 68c4752e998dca1d618380ce4e7d8ac52b710056 (patch) | |
| tree | 35e386030b072c189509bb2ed3adeaec5b0fd4d1 /examples/tour/src/iced_ggez/renderer | |
| parent | bb5cac49d028eb53c259ae58e3a007ebfb736fd4 (diff) | |
| parent | 05c7c39ecb8910c75b82dc4052a7720fb2d42b4a (diff) | |
| download | iced-68c4752e998dca1d618380ce4e7d8ac52b710056.tar.gz iced-68c4752e998dca1d618380ce4e7d8ac52b710056.tar.bz2 iced-68c4752e998dca1d618380ce4e7d8ac52b710056.zip | |
Merge pull request #17 from hecrj/web
Basic web support (core, native, and web crates)
Diffstat (limited to 'examples/tour/src/iced_ggez/renderer')
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/button.rs | 154 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/checkbox.rs | 94 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/debugger.rs | 32 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/image.rs | 76 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/radio.rs | 92 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/slider.rs | 93 | ||||
| -rw-r--r-- | examples/tour/src/iced_ggez/renderer/text.rs | 110 | 
7 files changed, 651 insertions, 0 deletions
| diff --git a/examples/tour/src/iced_ggez/renderer/button.rs b/examples/tour/src/iced_ggez/renderer/button.rs new file mode 100644 index 00000000..78a5de07 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/button.rs @@ -0,0 +1,154 @@ +use super::Renderer; +use ggez::graphics::{ +    self, Align, Color, DrawParam, Rect, Scale, Text, TextFragment, WHITE, +}; +use iced_native::{button, Button, Layout, Length, MouseCursor, Node, Style}; + +const LEFT: Rect = Rect { +    x: 0.0, +    y: 34.0, +    w: 6.0, +    h: 49.0, +}; + +const BACKGROUND: Rect = Rect { +    x: LEFT.w, +    y: LEFT.y, +    w: 1.0, +    h: LEFT.h, +}; + +const RIGHT: Rect = Rect { +    x: LEFT.h - LEFT.w, +    y: LEFT.y, +    w: LEFT.w, +    h: LEFT.h, +}; + +impl button::Renderer for Renderer<'_> { +    fn node<Message>(&self, button: &Button<'_, Message>) -> Node { +        let style = Style::default() +            .width(button.width) +            .height(Length::Units(LEFT.h as u16)) +            .min_width(Length::Units(100)) +            .align_self(button.align_self); + +        Node::new(style) +    } + +    fn draw<Message>( +        &mut self, +        button: &Button<'_, Message>, +        layout: Layout<'_>, +        cursor_position: iced_native::Point, +    ) -> MouseCursor { +        let mut bounds = layout.bounds(); +        let mouse_over = bounds.contains(cursor_position); + +        let mut state_offset = 0.0; + +        if mouse_over { +            if button.state.is_pressed() { +                bounds.y += 4.0; +                state_offset = RIGHT.x + RIGHT.w; +            } else { +                bounds.y -= 1.0; +            } +        } + +        let class_index = match button.class { +            button::Class::Primary => 0, +            button::Class::Secondary => 1, +            button::Class::Positive => 2, +        }; + +        let width = self.spritesheet.width() as f32; +        let height = self.spritesheet.height() as f32; + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (LEFT.x + state_offset) / width, +                y: (LEFT.y + class_index as f32 * LEFT.h) / height, +                w: LEFT.w / width, +                h: LEFT.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x, +                y: bounds.y, +            }, +            ..DrawParam::default() +        }); + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (BACKGROUND.x + state_offset) / width, +                y: (BACKGROUND.y + class_index as f32 * BACKGROUND.h) / height, +                w: BACKGROUND.w / width, +                h: BACKGROUND.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x + LEFT.w, +                y: bounds.y, +            }, +            scale: ggez::mint::Vector2 { +                x: bounds.width - LEFT.w - RIGHT.w, +                y: 1.0, +            }, +            ..DrawParam::default() +        }); + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (RIGHT.x + state_offset) / width, +                y: (RIGHT.y + class_index as f32 * RIGHT.h) / height, +                w: RIGHT.w / width, +                h: RIGHT.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x + bounds.width - RIGHT.w, +                y: bounds.y, +            }, +            ..DrawParam::default() +        }); + +        let mut text = Text::new(TextFragment { +            text: button.label.clone(), +            font: Some(self.font), +            scale: Some(Scale { x: 20.0, y: 20.0 }), +            ..Default::default() +        }); + +        text.set_bounds( +            ggez::mint::Point2 { +                x: bounds.width, +                y: bounds.height, +            }, +            Align::Center, +        ); + +        graphics::queue_text( +            self.context, +            &text, +            ggez::mint::Point2 { +                x: bounds.x, +                y: bounds.y + BACKGROUND.h / 4.0, +            }, +            Some(if mouse_over { +                WHITE +            } else { +                Color { +                    r: 0.9, +                    g: 0.9, +                    b: 0.9, +                    a: 1.0, +                } +            }), +        ); + +        if mouse_over { +            MouseCursor::Pointer +        } else { +            MouseCursor::OutOfBounds +        } +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/checkbox.rs b/examples/tour/src/iced_ggez/renderer/checkbox.rs new file mode 100644 index 00000000..807185d9 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/checkbox.rs @@ -0,0 +1,94 @@ +use super::Renderer; + +use ggez::graphics::{DrawParam, Rect}; +use iced_native::{ +    checkbox, text, Align, Checkbox, Column, Layout, Length, MouseCursor, Node, +    Row, Text, Widget, +}; + +const SPRITE: Rect = Rect { +    x: 98.0, +    y: 0.0, +    w: 28.0, +    h: 28.0, +}; + +impl checkbox::Renderer for Renderer<'_> +where +    Self: text::Renderer, +{ +    fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node { +        Row::<(), Self>::new() +            .spacing(15) +            .align_items(Align::Center) +            .push( +                Column::new() +                    .width(Length::Units(SPRITE.w as u16)) +                    .height(Length::Units(SPRITE.h as u16)), +            ) +            .push(Text::new(&checkbox.label)) +            .node(self) +    } + +    fn draw<Message>( +        &mut self, +        checkbox: &Checkbox<Message>, +        layout: Layout<'_>, +        cursor_position: iced_native::Point, +    ) -> MouseCursor { +        let bounds = layout.bounds(); +        let children: Vec<_> = layout.children().collect(); +        let text_bounds = children[1].bounds(); + +        let mut text = Text::new(&checkbox.label); + +        if let Some(label_color) = checkbox.label_color { +            text = text.color(label_color); +        } + +        text::Renderer::draw(self, &text, children[1]); + +        let mouse_over = bounds.contains(cursor_position) +            || text_bounds.contains(cursor_position); + +        let width = self.spritesheet.width() as f32; +        let height = self.spritesheet.height() as f32; + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 })) +                    / width, +                y: SPRITE.y / height, +                w: SPRITE.w / width, +                h: SPRITE.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x, +                y: bounds.y, +            }, +            ..DrawParam::default() +        }); + +        if checkbox.is_checked { +            self.sprites.add(DrawParam { +                src: Rect { +                    x: (SPRITE.x + SPRITE.w * 2.0) / width, +                    y: SPRITE.y / height, +                    w: SPRITE.w / width, +                    h: SPRITE.h / height, +                }, +                dest: ggez::mint::Point2 { +                    x: bounds.x, +                    y: bounds.y, +                }, +                ..DrawParam::default() +            }); +        } + +        if mouse_over { +            MouseCursor::Pointer +        } else { +            MouseCursor::OutOfBounds +        } +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/debugger.rs b/examples/tour/src/iced_ggez/renderer/debugger.rs new file mode 100644 index 00000000..ffb658af --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/debugger.rs @@ -0,0 +1,32 @@ +use super::{into_color, Renderer}; +use ggez::graphics::{DrawMode, MeshBuilder, Rect}; + +impl iced_native::renderer::Debugger for Renderer<'_> { +    fn explain( +        &mut self, +        layout: &iced_native::Layout<'_>, +        color: iced_native::Color, +    ) { +        let bounds = layout.bounds(); + +        let mut debug_mesh = +            self.debug_mesh.take().unwrap_or(MeshBuilder::new()); + +        debug_mesh.rectangle( +            DrawMode::stroke(1.0), +            Rect { +                x: bounds.x, +                y: bounds.y, +                w: bounds.width, +                h: bounds.height, +            }, +            into_color(color), +        ); + +        self.debug_mesh = Some(debug_mesh); + +        for child in layout.children() { +            self.explain(&child, color); +        } +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/image.rs b/examples/tour/src/iced_ggez/renderer/image.rs new file mode 100644 index 00000000..b12b65c3 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/image.rs @@ -0,0 +1,76 @@ +use super::Renderer; + +use ggez::{graphics, nalgebra}; +use iced_native::{image, Image, Layout, Length, Style}; + +pub struct Cache { +    images: std::collections::HashMap<String, graphics::Image>, +} + +impl Cache { +    pub fn new() -> Self { +        Self { +            images: std::collections::HashMap::new(), +        } +    } + +    fn get<'a>( +        &mut self, +        name: &'a str, +        context: &mut ggez::Context, +    ) -> graphics::Image { +        if let Some(image) = self.images.get(name) { +            return image.clone(); +        } + +        let mut image = graphics::Image::new(context, &format!("/{}", name)) +            .expect("Load ferris image"); + +        image.set_filter(graphics::FilterMode::Linear); + +        self.images.insert(name.to_string(), image.clone()); + +        image +    } +} + +impl<'a> image::Renderer<&'a str> for Renderer<'_> { +    fn node(&mut self, image: &Image<&'a str>) -> iced_native::Node { +        let ggez_image = self.images.get(image.handle, self.context); + +        let aspect_ratio = +            ggez_image.width() as f32 / ggez_image.height() as f32; + +        let mut style = Style::default().align_self(image.align_self); + +        style = match (image.width, image.height) { +            (Length::Units(width), _) => style.width(image.width).height( +                Length::Units((width as f32 / aspect_ratio).round() as u16), +            ), +            (_, _) => style +                .width(Length::Units(ggez_image.width())) +                .height(Length::Units(ggez_image.height())), +        }; + +        iced_native::Node::new(style) +    } + +    fn draw(&mut self, image: &Image<&'a str>, layout: Layout<'_>) { +        let image = self.images.get(image.handle, self.context); +        let bounds = layout.bounds(); + +        // We should probably use batches to draw images efficiently and keep +        // draw side-effect free, but this is good enough for the example. +        graphics::draw( +            self.context, +            &image, +            graphics::DrawParam::new() +                .dest(nalgebra::Point2::new(bounds.x, bounds.y)) +                .scale(nalgebra::Vector2::new( +                    bounds.width / image.width() as f32, +                    bounds.height / image.height() as f32, +                )), +        ) +        .expect("Draw image"); +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/radio.rs b/examples/tour/src/iced_ggez/renderer/radio.rs new file mode 100644 index 00000000..dbd29ecd --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/radio.rs @@ -0,0 +1,92 @@ +use super::Renderer; + +use ggez::graphics::{DrawParam, Rect}; +use iced_native::{ +    radio, text, Align, Column, Layout, Length, MouseCursor, Node, Point, +    Radio, Row, Text, Widget, +}; + +const SPRITE: Rect = Rect { +    x: 98.0, +    y: 28.0, +    w: 28.0, +    h: 28.0, +}; + +impl radio::Renderer for Renderer<'_> +where +    Self: text::Renderer, +{ +    fn node<Message>(&mut self, radio: &Radio<Message>) -> Node { +        Row::<(), Self>::new() +            .spacing(15) +            .align_items(Align::Center) +            .push( +                Column::new() +                    .width(Length::Units(SPRITE.w as u16)) +                    .height(Length::Units(SPRITE.h as u16)), +            ) +            .push(Text::new(&radio.label)) +            .node(self) +    } + +    fn draw<Message>( +        &mut self, +        radio: &Radio<Message>, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> MouseCursor { +        let children: Vec<_> = layout.children().collect(); + +        let mut text = Text::new(&radio.label); + +        if let Some(label_color) = radio.label_color { +            text = text.color(label_color); +        } + +        text::Renderer::draw(self, &text, children[1]); + +        let bounds = layout.bounds(); +        let mouse_over = bounds.contains(cursor_position); + +        let width = self.spritesheet.width() as f32; +        let height = self.spritesheet.height() as f32; + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 })) +                    / width, +                y: SPRITE.y / height, +                w: SPRITE.w / width, +                h: SPRITE.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x, +                y: bounds.y, +            }, +            ..DrawParam::default() +        }); + +        if radio.is_selected { +            self.sprites.add(DrawParam { +                src: Rect { +                    x: (SPRITE.x + SPRITE.w * 2.0) / width, +                    y: SPRITE.y / height, +                    w: SPRITE.w / width, +                    h: SPRITE.h / height, +                }, +                dest: ggez::mint::Point2 { +                    x: bounds.x, +                    y: bounds.y, +                }, +                ..DrawParam::default() +            }); +        } + +        if mouse_over { +            MouseCursor::Pointer +        } else { +            MouseCursor::OutOfBounds +        } +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/slider.rs b/examples/tour/src/iced_ggez/renderer/slider.rs new file mode 100644 index 00000000..60c40c55 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/slider.rs @@ -0,0 +1,93 @@ +use super::Renderer; + +use ggez::graphics::{DrawParam, Rect}; +use iced_native::{ +    slider, Layout, Length, MouseCursor, Node, Point, Slider, Style, +}; + +const RAIL: Rect = Rect { +    x: 98.0, +    y: 56.0, +    w: 1.0, +    h: 4.0, +}; + +const MARKER: Rect = Rect { +    x: RAIL.x + 28.0, +    y: RAIL.y, +    w: 16.0, +    h: 24.0, +}; + +impl slider::Renderer for Renderer<'_> { +    fn node<Message>(&self, slider: &Slider<'_, Message>) -> Node { +        let style = Style::default() +            .width(slider.width) +            .height(Length::Units(25)) +            .min_width(Length::Units(100)); + +        Node::new(style) +    } + +    fn draw<Message>( +        &mut self, +        slider: &Slider<'_, Message>, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> MouseCursor { +        let bounds = layout.bounds(); +        let width = self.spritesheet.width() as f32; +        let height = self.spritesheet.height() as f32; + +        self.sprites.add(DrawParam { +            src: Rect { +                x: RAIL.x / width, +                y: RAIL.y / height, +                w: RAIL.w / width, +                h: RAIL.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x + MARKER.w as f32 / 2.0, +                y: bounds.y + 12.5, +            }, +            scale: ggez::mint::Vector2 { +                x: bounds.width - MARKER.w as f32, +                y: 1.0, +            }, +            ..DrawParam::default() +        }); + +        let (range_start, range_end) = slider.range.clone().into_inner(); + +        let marker_offset = (bounds.width - MARKER.w as f32) +            * ((slider.value - range_start) +                / (range_end - range_start).max(1.0)); + +        let mouse_over = bounds.contains(cursor_position); +        let is_active = slider.state.is_dragging() || mouse_over; + +        self.sprites.add(DrawParam { +            src: Rect { +                x: (MARKER.x + (if is_active { MARKER.w } else { 0.0 })) +                    / width, +                y: MARKER.y / height, +                w: MARKER.w / width, +                h: MARKER.h / height, +            }, +            dest: ggez::mint::Point2 { +                x: bounds.x + marker_offset.round(), +                y: bounds.y +                    + (if slider.state.is_dragging() { 2.0 } else { 0.0 }), +            }, +            ..DrawParam::default() +        }); + +        if slider.state.is_dragging() { +            MouseCursor::Grabbing +        } else if mouse_over { +            MouseCursor::Grab +        } else { +            MouseCursor::OutOfBounds +        } +    } +} diff --git a/examples/tour/src/iced_ggez/renderer/text.rs b/examples/tour/src/iced_ggez/renderer/text.rs new file mode 100644 index 00000000..b51cc220 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/text.rs @@ -0,0 +1,110 @@ +use super::{into_color, Renderer}; +use ggez::graphics::{self, mint, Align, Scale, Text, TextFragment}; + +use iced_native::{text, Layout, Node, Style}; +use std::cell::RefCell; +use std::f32; + +impl text::Renderer for Renderer<'_> { +    fn node(&self, text: &iced_native::Text) -> Node { +        let font = self.font; +        let font_cache = graphics::font_cache(self.context); +        let content = String::from(&text.content); + +        // TODO: Investigate why stretch tries to measure this MANY times +        // with every ancestor's bounds. +        // Bug? Using the library wrong? I should probably open an issue on +        // the stretch repository. +        // I noticed that the first measure is the one that matters in +        // practice. Here, we use a RefCell to store the cached measurement. +        let measure = RefCell::new(None); +        let size = text.size.map(f32::from).unwrap_or(self.font_size); + +        let style = Style::default().width(text.width); + +        iced_native::Node::with_measure(style, move |bounds| { +            let mut measure = measure.borrow_mut(); + +            if measure.is_none() { +                let bounds = ( +                    match bounds.width { +                        iced_native::Number::Undefined => f32::INFINITY, +                        iced_native::Number::Defined(w) => w, +                    }, +                    match bounds.height { +                        iced_native::Number::Undefined => f32::INFINITY, +                        iced_native::Number::Defined(h) => h, +                    }, +                ); + +                let mut text = Text::new(TextFragment { +                    text: content.clone(), +                    font: Some(font), +                    scale: Some(Scale { x: size, y: size }), +                    ..Default::default() +                }); + +                text.set_bounds( +                    mint::Point2 { +                        x: bounds.0, +                        y: bounds.1, +                    }, +                    Align::Left, +                ); + +                let (width, height) = font_cache.dimensions(&text); + +                let size = iced_native::Size { +                    width: width as f32, +                    height: height as f32, +                }; + +                // If the text has no width boundary we avoid caching as the +                // layout engine may just be measuring text in a row. +                if bounds.0 == f32::INFINITY { +                    return size; +                } else { +                    *measure = Some(size); +                } +            } + +            measure.unwrap() +        }) +    } + +    fn draw(&mut self, text: &iced_native::Text, layout: Layout<'_>) { +        let size = text.size.map(f32::from).unwrap_or(self.font_size); +        let bounds = layout.bounds(); + +        let mut ggez_text = Text::new(TextFragment { +            text: text.content.clone(), +            font: Some(self.font), +            scale: Some(Scale { x: size, y: size }), +            ..Default::default() +        }); + +        ggez_text.set_bounds( +            mint::Point2 { +                x: bounds.width, +                y: bounds.height, +            }, +            match text.horizontal_alignment { +                text::HorizontalAlignment::Left => graphics::Align::Left, +                text::HorizontalAlignment::Center => graphics::Align::Center, +                text::HorizontalAlignment::Right => graphics::Align::Right, +            }, +        ); + +        graphics::queue_text( +            self.context, +            &ggez_text, +            mint::Point2 { +                x: bounds.x, +                y: bounds.y, +            }, +            text.color +                .or(Some(iced_native::Color::BLACK)) +                .map(into_color), +        ); +    } +} | 
