diff options
author | 2019-09-04 11:09:57 +0200 | |
---|---|---|
committer | 2019-09-04 11:09:57 +0200 | |
commit | c583a2174d28878a6b1a31288e80b96fac62e799 (patch) | |
tree | 36c80d9a0565980d6bbee62c548c8db142555ba2 /examples/ggez | |
parent | 2c35103035e47485f2fb049f86b3c00feb4b99d2 (diff) | |
download | iced-c583a2174d28878a6b1a31288e80b96fac62e799.tar.gz iced-c583a2174d28878a6b1a31288e80b96fac62e799.tar.bz2 iced-c583a2174d28878a6b1a31288e80b96fac62e799.zip |
Improve tour example
Diffstat (limited to 'examples/ggez')
-rw-r--r-- | examples/ggez/main.rs | 21 | ||||
-rw-r--r-- | examples/ggez/renderer.rs | 26 | ||||
-rw-r--r-- | examples/ggez/renderer/button.rs | 1 | ||||
-rw-r--r-- | examples/ggez/renderer/debugger.rs | 30 | ||||
-rw-r--r-- | examples/ggez/renderer/image.rs | 51 | ||||
-rw-r--r-- | examples/ggez/renderer/text.rs | 17 | ||||
-rw-r--r-- | examples/ggez/tour.rs | 381 | ||||
-rw-r--r-- | examples/ggez/widget.rs | 3 |
8 files changed, 344 insertions, 186 deletions
diff --git a/examples/ggez/main.rs b/examples/ggez/main.rs index 329bde81..0a6a2005 100644 --- a/examples/ggez/main.rs +++ b/examples/ggez/main.rs @@ -16,8 +16,8 @@ pub fn main() -> ggez::GameResult { let (context, event_loop) = { &mut ggez::ContextBuilder::new("iced", "ggez") .window_mode(ggez::conf::WindowMode { - width: 1280.0, - height: 1024.0, + width: 850.0, + height: 850.0, ..ggez::conf::WindowMode::default() }) .build()? @@ -39,6 +39,7 @@ pub fn main() -> ggez::GameResult { struct Game { spritesheet: graphics::Image, + font: graphics::Font, tour: Tour, events: Vec<iced::Event>, @@ -51,7 +52,8 @@ impl Game { Ok(Game { spritesheet: graphics::Image::new(context, "/ui.png").unwrap(), - tour: Tour::new(), + font: graphics::Font::new(context, "/Roboto-Regular.ttf").unwrap(), + tour: Tour::new(context), events: Vec::new(), cache: Some(iced::Cache::default()), @@ -126,7 +128,7 @@ impl event::EventHandler for Game { } fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult { - graphics::clear(context, [0.3, 0.3, 0.6, 1.0].into()); + graphics::clear(context, graphics::WHITE); let screen = graphics::screen_coordinates(context); @@ -134,14 +136,17 @@ impl event::EventHandler for Game { let layout = self.tour.layout(); let content = Column::new() - .width(screen.w as u32) - .height(screen.h as u32) + .width(screen.w as u16) + .height(screen.h as u16) .align_items(iced::Align::Center) .justify_content(iced::Justify::Center) .push(layout); - let renderer = - &mut Renderer::new(context, self.spritesheet.clone()); + let renderer = &mut Renderer::new( + context, + self.spritesheet.clone(), + self.font, + ); let mut ui = iced::UserInterface::build( content, diff --git a/examples/ggez/renderer.rs b/examples/ggez/renderer.rs index ccf12aba..8746dd96 100644 --- a/examples/ggez/renderer.rs +++ b/examples/ggez/renderer.rs @@ -1,24 +1,38 @@ mod button; mod checkbox; +mod debugger; +mod image; mod radio; mod slider; mod text; -use ggez::graphics::{self, spritebatch::SpriteBatch, Image}; +use ggez::graphics::{ + self, spritebatch::SpriteBatch, Font, Image, MeshBuilder, +}; use ggez::Context; pub struct Renderer<'a> { pub context: &'a mut Context, pub sprites: SpriteBatch, pub spritesheet: Image, + pub font: Font, + font_size: f32, + debug_mesh: Option<MeshBuilder>, } impl Renderer<'_> { - pub fn new(context: &mut Context, spritesheet: Image) -> Renderer { + pub fn new( + context: &mut Context, + spritesheet: Image, + font: Font, + ) -> Renderer { Renderer { context, sprites: SpriteBatch::new(spritesheet.clone()), spritesheet, + font, + font_size: 20.0, + debug_mesh: None, } } @@ -37,5 +51,13 @@ impl Renderer<'_> { graphics::FilterMode::Linear, ) .expect("Draw text"); + + if let Some(debug_mesh) = self.debug_mesh.take() { + let mesh = + debug_mesh.build(self.context).expect("Build debug mesh"); + + graphics::draw(self.context, &mesh, graphics::DrawParam::default()) + .expect("Draw debug mesh"); + } } } diff --git a/examples/ggez/renderer/button.rs b/examples/ggez/renderer/button.rs index fc3ea7ca..486e07ed 100644 --- a/examples/ggez/renderer/button.rs +++ b/examples/ggez/renderer/button.rs @@ -104,6 +104,7 @@ impl button::Renderer for Renderer<'_> { let mut text = Text::new(TextFragment { text: String::from(label), + font: Some(self.font), scale: Some(Scale { x: 20.0, y: 20.0 }), ..Default::default() }); diff --git a/examples/ggez/renderer/debugger.rs b/examples/ggez/renderer/debugger.rs new file mode 100644 index 00000000..98124795 --- /dev/null +++ b/examples/ggez/renderer/debugger.rs @@ -0,0 +1,30 @@ +use super::Renderer; +use ggez::graphics::{Color, DrawMode, MeshBuilder, Rect}; + +impl iced::renderer::Debugger for Renderer<'_> { + type Color = Color; + + fn explain(&mut self, layout: &iced::Layout<'_>, color: 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, + }, + color, + ); + + self.debug_mesh = Some(debug_mesh); + + for child in layout.children() { + self.explain(&child, color); + } + } +} diff --git a/examples/ggez/renderer/image.rs b/examples/ggez/renderer/image.rs new file mode 100644 index 00000000..c3ead5c9 --- /dev/null +++ b/examples/ggez/renderer/image.rs @@ -0,0 +1,51 @@ +use super::Renderer; + +use ggez::{graphics, nalgebra}; +use iced::image; + +impl image::Renderer<graphics::Image> for Renderer<'_> { + fn node( + &self, + style: iced::Style, + image: &graphics::Image, + width: Option<u16>, + height: Option<u16>, + _source: Option<iced::Rectangle<u16>>, + ) -> iced::Node { + let aspect_ratio = image.width() as f32 / image.height() as f32; + + let style = match (width, height) { + (Some(width), Some(height)) => style.width(width).height(height), + (Some(width), None) => style + .width(width) + .height((width as f32 / aspect_ratio).round() as u16), + (None, Some(height)) => style + .height(height) + .width((height as f32 * aspect_ratio).round() as u16), + (None, None) => style.width(image.width()).height(image.height()), + }; + + iced::Node::new(style) + } + + fn draw( + &mut self, + image: &graphics::Image, + bounds: iced::Rectangle, + _source: Option<iced::Rectangle<u16>>, + ) { + // 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/ggez/renderer/text.rs b/examples/ggez/renderer/text.rs index a6f782fc..ecf1481e 100644 --- a/examples/ggez/renderer/text.rs +++ b/examples/ggez/renderer/text.rs @@ -6,7 +6,13 @@ use std::cell::RefCell; use std::f32; impl text::Renderer<Color> for Renderer<'_> { - fn node(&self, style: iced::Style, content: &str, size: f32) -> iced::Node { + fn node( + &self, + style: iced::Style, + content: &str, + size: Option<u16>, + ) -> iced::Node { + let font = self.font; let font_cache = graphics::font_cache(self.context); let content = String::from(content); @@ -17,6 +23,7 @@ impl text::Renderer<Color> for Renderer<'_> { // 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 = size.map(f32::from).unwrap_or(self.font_size); iced::Node::with_measure(style, move |bounds| { let mut measure = measure.borrow_mut(); @@ -35,6 +42,7 @@ impl text::Renderer<Color> for Renderer<'_> { let mut text = Text::new(TextFragment { text: content.clone(), + font: Some(font), scale: Some(Scale { x: size, y: size }), ..Default::default() }); @@ -71,13 +79,16 @@ impl text::Renderer<Color> for Renderer<'_> { &mut self, bounds: iced::Rectangle, content: &str, - size: f32, + size: Option<u16>, color: Option<Color>, horizontal_alignment: text::HorizontalAlignment, _vertical_alignment: text::VerticalAlignment, ) { + let size = size.map(f32::from).unwrap_or(self.font_size); + let mut text = Text::new(TextFragment { text: String::from(content), + font: Some(self.font), scale: Some(Scale { x: size, y: size }), ..Default::default() }); @@ -101,7 +112,7 @@ impl text::Renderer<Color> for Renderer<'_> { x: bounds.x, y: bounds.y, }, - color, + color.or(Some(graphics::BLACK)), ); } } diff --git a/examples/ggez/tour.rs b/examples/ggez/tour.rs index 19982aa4..c2126675 100644 --- a/examples/ggez/tour.rs +++ b/examples/ggez/tour.rs @@ -1,22 +1,26 @@ use super::widget::{ - button, slider, Button, Checkbox, Column, Element, Radio, Row, Slider, Text, + button, slider, Button, Checkbox, Column, Element, Image, Radio, Row, + Slider, Text, }; -use ggez::graphics::{Color, BLACK}; +use ggez::graphics::{self, Color, FilterMode, BLACK}; +use ggez::Context; use iced::{text::HorizontalAlignment, Align}; pub struct Tour { steps: Steps, back_button: button::State, next_button: button::State, + debug: bool, } impl Tour { - pub fn new() -> Tour { + pub fn new(context: &mut Context) -> Tour { Tour { - steps: Steps::new(), + steps: Steps::new(context), back_button: button::State::new(), next_button: button::State::new(), + debug: false, } } @@ -29,7 +33,7 @@ impl Tour { self.steps.advance(); } Message::StepMessage(step_msg) => { - self.steps.update(step_msg); + self.steps.update(step_msg, &mut self.debug); } } } @@ -39,6 +43,7 @@ impl Tour { steps, back_button, next_button, + .. } = self; let mut controls = Row::new(); @@ -59,12 +64,18 @@ impl Tour { ); } - Column::new() + let element: Element<_> = Column::new() .max_width(500) .spacing(20) - .push(steps.layout().map(Message::StepMessage)) + .push(steps.layout(self.debug).map(Message::StepMessage)) .push(controls) - .into() + .into(); + + if self.debug { + element.explain(BLACK) + } else { + element + } } } @@ -81,44 +92,52 @@ struct Steps { } impl Steps { - fn new() -> Steps { + fn new(context: &mut Context) -> Steps { Steps { steps: vec![ Step::Welcome, - Step::Buttons { - primary: button::State::new(), - secondary: button::State::new(), - positive: button::State::new(), - }, - Step::Checkbox { is_checked: false }, - Step::Radio { selection: None }, Step::Slider { state: slider::State::new(), value: 50, }, + Step::RowsAndColumns { + layout: Layout::Row, + spacing_slider: slider::State::new(), + spacing: 20, + }, Step::Text { size_slider: slider::State::new(), size: 30, color_sliders: [slider::State::new(); 3], color: BLACK, }, - Step::RowsAndColumns { - layout: Layout::Row, - spacing_slider: slider::State::new(), - spacing: 20, + Step::Radio { selection: None }, + Step::Image { + ferris: { + let mut image = + graphics::Image::new(context, "/ferris.png") + .expect("Load ferris image"); + + image.set_filter(FilterMode::Linear); + + image + }, + width: 300, + slider: slider::State::new(), }, + Step::Debugger, Step::End, ], current: 0, } } - fn update(&mut self, msg: StepMessage) { - self.steps[self.current].update(msg); + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + self.steps[self.current].update(msg, debug); } - fn layout(&mut self) -> Element<StepMessage> { - self.steps[self.current].layout() + fn layout(&mut self, debug: bool) -> Element<StepMessage> { + self.steps[self.current].layout(debug) } fn advance(&mut self) { @@ -145,52 +164,51 @@ impl Steps { enum Step { Welcome, - Buttons { - primary: button::State, - secondary: button::State, - positive: button::State, - }, - Checkbox { - is_checked: bool, - }, - Radio { - selection: Option<Language>, - }, Slider { state: slider::State, value: u16, }, + RowsAndColumns { + layout: Layout, + spacing_slider: slider::State, + spacing: u16, + }, Text { size_slider: slider::State, size: u16, color_sliders: [slider::State; 3], color: Color, }, - RowsAndColumns { - layout: Layout, - spacing_slider: slider::State, - spacing: u16, + Radio { + selection: Option<Language>, }, + Image { + ferris: graphics::Image, + width: u16, + slider: slider::State, + }, + Debugger, End, } #[derive(Debug, Clone, Copy)] pub enum StepMessage { - CheckboxToggled(bool), - LanguageSelected(Language), SliderChanged(f32), - TextSizeChanged(f32), - TextColorChanged(Color), LayoutChanged(Layout), SpacingChanged(f32), + TextSizeChanged(f32), + TextColorChanged(Color), + LanguageSelected(Language), + ImageWidthChanged(f32), + DebugToggled(bool), } impl<'a> Step { - fn update(&mut self, msg: StepMessage) { + fn update(&mut self, msg: StepMessage, debug: &mut bool) { match msg { - StepMessage::CheckboxToggled(value) => { - if let Step::Checkbox { is_checked } = self { - *is_checked = value; + StepMessage::DebugToggled(value) => { + if let Step::Debugger = self { + *debug = value; } } StepMessage::LanguageSelected(language) => { @@ -223,31 +241,30 @@ impl<'a> Step { *spacing = new_spacing.round() as u16; } } + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width.round() as u16; + } + } }; } fn can_continue(&self) -> bool { match self { Step::Welcome => true, - Step::Buttons { .. } => true, - Step::Checkbox { is_checked } => *is_checked, Step::Radio { selection } => *selection == Some(Language::Rust), Step::Slider { .. } => true, Step::Text { .. } => true, + Step::Image { .. } => true, Step::RowsAndColumns { .. } => true, + Step::Debugger => true, Step::End => false, } } - fn layout(&mut self) -> Element<StepMessage> { + fn layout(&mut self, debug: bool) -> Element<StepMessage> { match self { Step::Welcome => Self::welcome().into(), - Step::Buttons { - primary, - secondary, - positive, - } => Self::buttons(primary, secondary, positive).into(), - Step::Checkbox { is_checked } => Self::checkbox(*is_checked).into(), Step::Radio { selection } => Self::radio(*selection).into(), Step::Slider { state, value } => Self::slider(state, *value).into(), Step::Text { @@ -256,6 +273,11 @@ impl<'a> Step { color_sliders, color, } => Self::text(size_slider, *size, color_sliders, *color).into(), + Step::Image { + ferris, + width, + slider, + } => Self::image(ferris.clone(), *width, slider).into(), Step::RowsAndColumns { layout, spacing_slider, @@ -263,6 +285,7 @@ impl<'a> Step { } => { Self::rows_and_columns(*layout, spacing_slider, *spacing).into() } + Step::Debugger => Self::debugger(debug).into(), Step::End => Self::end().into(), } } @@ -277,80 +300,26 @@ impl<'a> Step { fn welcome() -> Column<'a, StepMessage> { Self::container("Welcome!") .push(Text::new( - "This is a tour that introduces some of the features and \ - concepts related with UI development in Iced.", + "This a simple tour meant to showcase a bunch of widgets that \ + can be easily implemented on top of Iced.", )) .push(Text::new( - "You will need to interact with the UI in order to reach the \ - end!", - )) - } - - fn buttons( - primary: &'a mut button::State, - secondary: &'a mut button::State, - positive: &'a mut button::State, - ) -> Column<'a, StepMessage> { - Self::container("Button") - .push(Text::new("A button can fire actions when clicked.")) - .push(Text::new( - "As of now, there are 3 different types of buttons: \ - primary, secondary, and positive.", - )) - .push(Button::new(primary, "Primary")) - .push( - Button::new(secondary, "Secondary") - .class(button::Class::Secondary), - ) - .push( - Button::new(positive, "Positive") - .class(button::Class::Positive), - ) - .push(Text::new( - "Additional types will be added in the near future! Choose \ - each type smartly depending on the situation.", + "Iced is a renderer-agnostic GUI library for Rust focused on \ + simplicity and type-safety. It is heavily inspired by Elm.", )) - } - - fn checkbox(is_checked: bool) -> Column<'a, StepMessage> { - Self::container("Checkbox") .push(Text::new( - "A box that can be checked. Useful to build toggle controls.", - )) - .push(Checkbox::new( - is_checked, - "Show \"Next\" button", - StepMessage::CheckboxToggled, + "It was originally born as part of Coffee, an opinionated \ + 2D game engine for Rust.", )) .push(Text::new( - "A checkbox always has a label, and both the checkbox and its \ - label can be clicked to toggle it.", + "Iced does not provide a built-in renderer. This example runs \ + on a fairly simple renderer built on top of ggez, another \ + game library.", )) - } - - fn radio(selection: Option<Language>) -> Column<'a, StepMessage> { - let question = Column::new() - .padding(20) - .spacing(10) - .push(Text::new("Iced is written in...")) - .push(Language::all().iter().cloned().fold( - Column::new().padding(10).spacing(20), - |choices, language| { - choices.push(Radio::new( - language, - language.into(), - selection, - StepMessage::LanguageSelected, - )) - }, - )); - - Self::container("Radio button") .push(Text::new( - "A radio button is normally used to represent a choice. Like \ - a checkbox, it always has a label.", + "You will need to interact with the UI in order to reach the \ + end!", )) - .push(question) } fn slider( @@ -378,55 +347,6 @@ impl<'a> Step { ) } - fn text( - size_slider: &'a mut slider::State, - size: u16, - color_sliders: &'a mut [slider::State; 3], - color: Color, - ) -> Column<'a, StepMessage> { - let size_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("You can change its size:")) - .push( - Text::new(&format!("This text is {} points", size)).size(size), - ) - .push(Slider::new( - size_slider, - 10.0..=50.0, - size as f32, - StepMessage::TextSizeChanged, - )); - - let [red, green, blue] = color_sliders; - let color_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("And its color:")) - .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), - ); - - Self::container("Text") - .push(Text::new( - "Text is probably the most essential widget for your UI. \ - It will try to adapt to the dimensions of its container.", - )) - .push(size_section) - .push(color_section) - } - fn rows_and_columns( layout: Layout, spacing_slider: &'a mut slider::State, @@ -463,7 +383,7 @@ impl<'a> Step { .spacing(10) .push(Slider::new( spacing_slider, - 0.0..=100.0, + 0.0..=80.0, spacing as f32, StepMessage::SpacingChanged, )) @@ -489,10 +409,127 @@ impl<'a> Step { .push(spacing_section) } + fn text( + size_slider: &'a mut slider::State, + size: u16, + color_sliders: &'a mut [slider::State; 3], + color: Color, + ) -> Column<'a, StepMessage> { + let size_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("You can change its size:")) + .push( + Text::new(&format!("This text is {} pixels", size)).size(size), + ) + .push(Slider::new( + size_slider, + 10.0..=70.0, + size as f32, + StepMessage::TextSizeChanged, + )); + + let [red, green, blue] = color_sliders; + let color_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("And its color:")) + .push(Text::new(&format!("{:?}", color)).color(color)) + .push( + Row::new() + .spacing(10) + .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { + StepMessage::TextColorChanged(Color { r, ..color }) + })) + .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { + StepMessage::TextColorChanged(Color { g, ..color }) + })) + .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { + StepMessage::TextColorChanged(Color { b, ..color }) + })), + ); + + Self::container("Text") + .push(Text::new( + "Text is probably the most essential widget for your UI. \ + It will try to adapt to the dimensions of its container.", + )) + .push(size_section) + .push(color_section) + } + + fn radio(selection: Option<Language>) -> Column<'a, StepMessage> { + let question = Column::new() + .padding(20) + .spacing(10) + .push(Text::new("Iced is written in...").size(24)) + .push(Language::all().iter().cloned().fold( + Column::new().padding(10).spacing(20), + |choices, language| { + choices.push(Radio::new( + language, + language.into(), + selection, + StepMessage::LanguageSelected, + )) + }, + )); + + Self::container("Radio button") + .push(Text::new( + "A radio button is normally used to represent a choice... \ + Surprise test!", + )) + .push(question) + .push(Text::new( + "Iced works very well with iterators! The list above is \ + basically created by folding a column over the different \ + choices, creating a radio button for each one of them!", + )) + } + + fn image( + ferris: graphics::Image, + width: u16, + slider: &'a mut slider::State, + ) -> Column<'a, StepMessage> { + Self::container("Image") + .push(Text::new("An image that tries to keep its aspect ratio.")) + .push(Image::new(ferris).width(width).align_self(Align::Center)) + .push(Slider::new( + slider, + 100.0..=500.0, + width as f32, + StepMessage::ImageWidthChanged, + )) + .push( + Text::new(&format!("Width: {} px", width.to_string())) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn debugger(debug: bool) -> Column<'a, StepMessage> { + Self::container("Debugger") + .push(Text::new( + "You can ask Iced to visually explain the layouting of the \ + different elements comprising your UI!", + )) + .push(Text::new( + "Give it a shot! Check the following checkbox to be able to \ + see element boundaries.", + )) + .push(Checkbox::new( + debug, + "Explain layout", + StepMessage::DebugToggled, + )) + .push(Text::new("Feel free to go back and take a look.")) + } + fn end() -> Column<'a, StepMessage> { Self::container("You reached the end!") .push(Text::new( - "This tour will be extended as more features are added.", + "This tour will be updated as more features are added.", )) .push(Text::new("Make sure to keep an eye on it!")) } diff --git a/examples/ggez/widget.rs b/examples/ggez/widget.rs index a5fcccc6..9a141c83 100644 --- a/examples/ggez/widget.rs +++ b/examples/ggez/widget.rs @@ -1,12 +1,13 @@ use super::Renderer; -use ggez::graphics::Color; +use ggez::graphics::{self, Color}; pub use iced::{button, slider, Button, Slider}; pub type Text = iced::Text<Color>; pub type Checkbox<Message> = iced::Checkbox<Color, Message>; pub type Radio<Message> = iced::Radio<Color, Message>; +pub type Image = iced::Image<graphics::Image>; pub type Column<'a, Message> = iced::Column<'a, Message, Renderer<'a>>; pub type Row<'a, Message> = iced::Row<'a, Message, Renderer<'a>>; |