From c583a2174d28878a6b1a31288e80b96fac62e799 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Sep 2019 11:09:57 +0200 Subject: Improve tour example --- Cargo.toml | 2 +- examples/ggez/main.rs | 21 +- examples/ggez/renderer.rs | 26 ++- examples/ggez/renderer/button.rs | 1 + examples/ggez/renderer/debugger.rs | 30 +++ examples/ggez/renderer/image.rs | 51 +++++ examples/ggez/renderer/text.rs | 17 +- examples/ggez/tour.rs | 381 +++++++++++++++++++--------------- examples/ggez/widget.rs | 3 +- examples/resources/Roboto-LICENSE | 202 ++++++++++++++++++ examples/resources/Roboto-Regular.ttf | Bin 0 -> 171272 bytes examples/resources/ferris.png | Bin 0 -> 33061 bytes src/lib.rs | 4 +- src/style.rs | 20 +- src/widget.rs | 6 +- src/widget/button.rs | 2 +- src/widget/checkbox.rs | 2 +- src/widget/column.rs | 10 +- src/widget/image.rs | 63 +++++- src/widget/progress_bar.rs | 2 +- src/widget/radio.rs | 2 +- src/widget/row.rs | 10 +- src/widget/slider.rs | 2 +- src/widget/text.rs | 18 +- 24 files changed, 640 insertions(+), 235 deletions(-) create mode 100644 examples/ggez/renderer/debugger.rs create mode 100644 examples/ggez/renderer/image.rs create mode 100644 examples/resources/Roboto-LICENSE create mode 100644 examples/resources/Roboto-Regular.ttf create mode 100644 examples/resources/ferris.png diff --git a/Cargo.toml b/Cargo.toml index 82279f05..1c139e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ twox-hash = "1.5" winit = { version = "0.20.0-alpha3", optional = true } [dev-dependencies] -ggez = { version = "0.5", git = "https://github.com/hecrj/ggez.git", branch = "font-cache" } +ggez = { version = "0.5", git = "https://github.com/hecrj/ggez.git" } 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, @@ -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, } 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 for Renderer<'_> { + fn node( + &self, + style: iced::Style, + image: &graphics::Image, + width: Option, + height: Option, + _source: Option>, + ) -> 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>, + ) { + // 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 for Renderer<'_> { - fn node(&self, style: iced::Style, content: &str, size: f32) -> iced::Node { + fn node( + &self, + style: iced::Style, + content: &str, + size: Option, + ) -> 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 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 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 for Renderer<'_> { &mut self, bounds: iced::Rectangle, content: &str, - size: f32, + size: Option, color: Option, 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 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 { - self.steps[self.current].layout() + fn layout(&mut self, debug: bool) -> Element { + 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, - }, 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, }, + 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 { + fn layout(&mut self, debug: bool) -> Element { 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) -> 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) -> 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; pub type Checkbox = iced::Checkbox; pub type Radio = iced::Radio; +pub type Image = iced::Image; pub type Column<'a, Message> = iced::Column<'a, Message, Renderer<'a>>; pub type Row<'a, Message> = iced::Row<'a, Message, Renderer<'a>>; diff --git a/examples/resources/Roboto-LICENSE b/examples/resources/Roboto-LICENSE new file mode 100644 index 00000000..75b52484 --- /dev/null +++ b/examples/resources/Roboto-LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/resources/Roboto-Regular.ttf b/examples/resources/Roboto-Regular.ttf new file mode 100644 index 00000000..2b6392ff Binary files /dev/null and b/examples/resources/Roboto-Regular.ttf differ diff --git a/examples/resources/ferris.png b/examples/resources/ferris.png new file mode 100644 index 00000000..ebce1a14 Binary files /dev/null and b/examples/resources/ferris.png differ diff --git a/src/lib.rs b/src/lib.rs index 88a5e81e..c1c18b41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ //! # } //! # //! # impl text::Renderer<[f32; 4]> for Renderer { -//! # fn node(&self, style: Style, _content: &str, _size: f32) -> Node { +//! # fn node(&self, style: Style, _content: &str, _size: Option) -> Node { //! # Node::new(style) //! # } //! # @@ -105,7 +105,7 @@ //! # &mut self, //! # _bounds: Rectangle, //! # _content: &str, -//! # _size: f32, +//! # _size: Option, //! # _color: Option<[f32; 4]>, //! # _horizontal_alignment: HorizontalAlignment, //! # _vertical_alignment: VerticalAlignment, diff --git a/src/style.rs b/src/style.rs index 9e1f7df2..575ea366 100644 --- a/src/style.rs +++ b/src/style.rs @@ -11,7 +11,7 @@ impl Style { /// Defines the width of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.0.size.width = style::Dimension::Points(width as f32); self } @@ -19,7 +19,7 @@ impl Style { /// Defines the height of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn height(mut self, height: u32) -> Self { + pub fn height(mut self, height: u16) -> Self { self.0.size.height = style::Dimension::Points(height as f32); self } @@ -27,7 +27,7 @@ impl Style { /// Defines the minimum width of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn min_width(mut self, min_width: u32) -> Self { + pub fn min_width(mut self, min_width: u16) -> Self { self.0.min_size.width = style::Dimension::Points(min_width as f32); self } @@ -35,7 +35,7 @@ impl Style { /// Defines the maximum width of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u16) -> Self { self.0.max_size.width = style::Dimension::Points(max_width as f32); self.fill_width() } @@ -43,16 +43,18 @@ impl Style { /// Defines the minimum height of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn min_height(mut self, min_height: u32) -> Self { - self.0.min_size.height = style::Dimension::Points(min_height as f32); + pub fn min_height(mut self, min_height: u16) -> Self { + self.0.min_size.height = + style::Dimension::Points(f32::from(min_height)); self } /// Defines the maximum height of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.0.max_size.height = style::Dimension::Points(max_height as f32); + pub fn max_height(mut self, max_height: u16) -> Self { + self.0.max_size.height = + style::Dimension::Points(f32::from(max_height)); self.fill_height() } @@ -100,7 +102,7 @@ impl Style { /// Sets the padding of a [`Node`] in pixels. /// /// [`Node`]: struct.Node.html - pub fn padding(mut self, px: u32) -> Self { + pub fn padding(mut self, px: u16) -> Self { self.0.padding = stretch::geometry::Rect { start: style::Dimension::Points(px as f32), end: style::Dimension::Points(px as f32), diff --git a/src/widget.rs b/src/widget.rs index b8ecb409..30606934 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -26,16 +26,16 @@ mod row; pub mod button; pub mod checkbox; pub mod image; -pub mod progress_bar; +//pub mod progress_bar; pub mod radio; pub mod slider; pub mod text; pub use button::Button; pub use checkbox::Checkbox; -pub use image::Image; pub use column::Column; -pub use progress_bar::ProgressBar; +pub use image::Image; +//pub use progress_bar::ProgressBar; pub use radio::Radio; pub use row::Row; pub use slider::Slider; diff --git a/src/widget/button.rs b/src/widget/button.rs index 14fd3852..abcdbfeb 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -83,7 +83,7 @@ impl<'a, Message> Button<'a, Message> { /// Sets the width of the [`Button`] in pixels. /// /// [`Button`]: struct.Button.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } diff --git a/src/widget/checkbox.rs b/src/widget/checkbox.rs index 34d5df53..c60807fd 100644 --- a/src/widget/checkbox.rs +++ b/src/widget/checkbox.rs @@ -142,7 +142,7 @@ where renderer, text_bounds, &self.label, - 20.0, + None, self.label_color, text::HorizontalAlignment::Left, text::VerticalAlignment::Top, diff --git a/src/widget/column.rs b/src/widget/column.rs index 903de897..ff754e98 100644 --- a/src/widget/column.rs +++ b/src/widget/column.rs @@ -55,7 +55,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the padding of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn padding(mut self, px: u32) -> Self { + pub fn padding(mut self, px: u16) -> Self { self.style = self.style.padding(px); self } @@ -63,7 +63,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the width of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } @@ -71,7 +71,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn height(mut self, height: u32) -> Self { + pub fn height(mut self, height: u16) -> Self { self.style = self.style.height(height); self } @@ -79,7 +79,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum width of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u16) -> Self { self.style = self.style.max_width(max_width); self } @@ -87,7 +87,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u16) -> Self { self.style = self.style.max_height(max_height); self } diff --git a/src/widget/image.rs b/src/widget/image.rs index 8cbcd02c..161dc963 100644 --- a/src/widget/image.rs +++ b/src/widget/image.rs @@ -1,7 +1,8 @@ //! Display images in your user interface. use crate::{ - Element, Hasher, Layout, MouseCursor, Node, Point, Rectangle, Style, Widget, + Align, Element, Hasher, Layout, MouseCursor, Node, Point, Rectangle, Style, + Widget, }; use std::hash::Hash; @@ -25,6 +26,8 @@ use std::hash::Hash; pub struct Image { image: I, source: Option>, + width: Option, + height: Option, style: Style, } @@ -32,6 +35,8 @@ impl std::fmt::Debug for Image { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Image") .field("source", &self.source) + .field("width", &self.width) + .field("height", &self.height) .field("style", &self.style) .finish() } @@ -45,7 +50,9 @@ impl Image { Image { image, source: None, - style: Style::default().fill_width().fill_height(), + width: None, + height: None, + style: Style::default(), } } @@ -60,16 +67,27 @@ impl Image { /// Sets the width of the [`Image`] boundaries in pixels. /// /// [`Image`]: struct.Image.html - pub fn width(mut self, width: u32) -> Self { - self.style = self.style.width(width); + pub fn width(mut self, width: u16) -> Self { + self.width = Some(width); self } /// Sets the height of the [`Image`] boundaries in pixels. /// /// [`Image`]: struct.Image.html - pub fn height(mut self, height: u32) -> Self { - self.style = self.style.height(height); + pub fn height(mut self, height: u16) -> Self { + self.height = Some(height); + self + } + + /// Sets the alignment of the [`Image`] itself. + /// + /// This is useful if you want to override the default alignment given by + /// the parent container. + /// + /// [`Image`]: struct.Button.html + pub fn align_self(mut self, align: Align) -> Self { + self.style = self.style.align_self(align); self } } @@ -79,8 +97,14 @@ where Renderer: self::Renderer, I: Clone, { - fn node(&self, _renderer: &Renderer) -> Node { - Node::new(self.style) + fn node(&self, renderer: &Renderer) -> Node { + renderer.node( + self.style, + &self.image, + self.width, + self.height, + self.source, + ) } fn draw( @@ -89,13 +113,15 @@ where layout: Layout<'_>, _cursor_position: Point, ) -> MouseCursor { - renderer.draw(layout.bounds(), self.image.clone(), self.source); + renderer.draw(&self.image, layout.bounds(), self.source); MouseCursor::OutOfBounds } fn hash_layout(&self, state: &mut Hasher) { self.style.hash(state); + self.width.hash(state); + self.height.hash(state); } } @@ -107,6 +133,23 @@ where /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html pub trait Renderer { + /// Creates a [`Node`] with the given [`Style`] for the provided [`Image`] + /// and its size. + /// + /// You should probably keep the original aspect ratio, if possible. + /// + /// [`Node`]: ../../struct.Node.html + /// [`Style`]: ../../struct.Style.html + /// [`Image`]: struct.Image.html + fn node( + &self, + style: Style, + image: &I, + width: Option, + height: Option, + source: Option>, + ) -> Node; + /// Draws an [`Image`]. /// /// It receives: @@ -118,8 +161,8 @@ pub trait Renderer { /// [`Image`]: struct.Image.html fn draw( &mut self, + image: &I, bounds: Rectangle, - image: I, source: Option>, ); } diff --git a/src/widget/progress_bar.rs b/src/widget/progress_bar.rs index 645c7277..d4499160 100644 --- a/src/widget/progress_bar.rs +++ b/src/widget/progress_bar.rs @@ -47,7 +47,7 @@ impl ProgressBar { /// Sets the width of the [`ProgressBar`] in pixels. /// /// [`ProgressBar`]: struct.ProgressBar.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } diff --git a/src/widget/radio.rs b/src/widget/radio.rs index a59d52aa..28353ef4 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -152,7 +152,7 @@ where renderer, text_bounds, &self.label, - 20.0, + None, self.label_color, text::HorizontalAlignment::Left, text::VerticalAlignment::Top, diff --git a/src/widget/row.rs b/src/widget/row.rs index 7b7033a1..959528dc 100644 --- a/src/widget/row.rs +++ b/src/widget/row.rs @@ -52,7 +52,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the padding of the [`Row`] in pixels. /// /// [`Row`]: struct.Row.html - pub fn padding(mut self, px: u32) -> Self { + pub fn padding(mut self, px: u16) -> Self { self.style = self.style.padding(px); self } @@ -60,7 +60,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the width of the [`Row`] in pixels. /// /// [`Row`]: struct.Row.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } @@ -68,7 +68,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the height of the [`Row`] in pixels. /// /// [`Row`]: struct.Row.html - pub fn height(mut self, height: u32) -> Self { + pub fn height(mut self, height: u16) -> Self { self.style = self.style.height(height); self } @@ -76,7 +76,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum width of the [`Row`] in pixels. /// /// [`Row`]: struct.Row.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u16) -> Self { self.style = self.style.max_width(max_width); self } @@ -84,7 +84,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum height of the [`Row`] in pixels. /// /// [`Row`]: struct.Row.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u16) -> Self { self.style = self.style.max_height(max_height); self } diff --git a/src/widget/slider.rs b/src/widget/slider.rs index c7adbb51..cdec9ec4 100644 --- a/src/widget/slider.rs +++ b/src/widget/slider.rs @@ -93,7 +93,7 @@ impl<'a, Message> Slider<'a, Message> { /// Sets the width of the [`Slider`] in pixels. /// /// [`Slider`]: struct.Slider.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } diff --git a/src/widget/text.rs b/src/widget/text.rs index 7b62e5cb..59b599bb 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -30,7 +30,7 @@ use std::hash::Hash; #[derive(Debug, Clone)] pub struct Text { content: String, - size: u16, + size: Option, color: Option, style: Style, horizontal_alignment: HorizontalAlignment, @@ -44,7 +44,7 @@ impl Text { pub fn new(label: &str) -> Self { Text { content: String::from(label), - size: 20, + size: None, color: None, style: Style::default().fill_width(), horizontal_alignment: HorizontalAlignment::Left, @@ -56,7 +56,7 @@ impl Text { /// /// [`Text`]: struct.Text.html pub fn size(mut self, size: u16) -> Self { - self.size = size; + self.size = Some(size); self } @@ -71,7 +71,7 @@ impl Text { /// Sets the width of the [`Text`] boundaries in pixels. /// /// [`Text`]: struct.Text.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } @@ -79,7 +79,7 @@ impl Text { /// Sets the height of the [`Text`] boundaries in pixels. /// /// [`Text`]: struct.Text.html - pub fn height(mut self, height: u32) -> Self { + pub fn height(mut self, height: u16) -> Self { self.style = self.style.height(height); self } @@ -112,7 +112,7 @@ where Renderer: self::Renderer, { fn node(&self, renderer: &Renderer) -> Node { - renderer.node(self.style, &self.content, f32::from(self.size)) + renderer.node(self.style, &self.content, self.size) } fn draw( @@ -124,7 +124,7 @@ where renderer.draw( layout.bounds(), &self.content, - f32::from(self.size), + self.size, self.color, self.horizontal_alignment, self.vertical_alignment, @@ -160,7 +160,7 @@ pub trait Renderer { /// [`Style`]: ../../struct.Style.html /// [`Text`]: struct.Text.html /// [`Node::with_measure`]: ../../struct.Node.html#method.with_measure - fn node(&self, style: Style, content: &str, size: f32) -> Node; + fn node(&self, style: Style, content: &str, size: Option) -> Node; /// Draws a [`Text`] fragment. /// @@ -179,7 +179,7 @@ pub trait Renderer { &mut self, bounds: Rectangle, content: &str, - size: f32, + size: Option, color: Option, horizontal_alignment: HorizontalAlignment, vertical_alignment: VerticalAlignment, -- cgit