diff --git a/Cargo.toml b/Cargo.toml index 526ddbf..119ba8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,7 @@ debug = [] image = "0.21" nalgebra = "0.18" rayon = "1.0" -stretch = "0.2" -twox-hash = "1.3" +iced = { version = "0.1.0-alpha", features = ["winit"] } lyon_tessellation = "0.13" gilrs = "0.7" winit = "0.20.0-alpha3" diff --git a/examples/counter.rs b/examples/counter.rs index b520e5c..cc370ac 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -69,8 +69,8 @@ impl UserInterface for Counter { fn layout(&mut self, window: &Window) -> Element { Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .align_items(Align::Center) .justify_content(Justify::Center) .spacing(20) diff --git a/examples/gamepad.rs b/examples/gamepad.rs index 0159201..708439d 100644 --- a/examples/gamepad.rs +++ b/examples/gamepad.rs @@ -75,8 +75,8 @@ impl UserInterface for GamepadExample { fn layout(&mut self, window: &Window) -> Element<()> { Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .align_items(Align::Center) .justify_content(Justify::Center) .push( diff --git a/examples/image.rs b/examples/image.rs index 327140a..58c78af 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -1,10 +1,10 @@ use coffee::graphics::{ - Color, Frame, HorizontalAlignment, VerticalAlignment, Window, - WindowSettings, self, + self, Color, Frame, HorizontalAlignment, VerticalAlignment, Window, + WindowSettings, }; use coffee::load::Task; use coffee::ui::{ - Align, Column, Element, Justify, Renderer, Text, UserInterface, Image, + Align, Column, Element, Image, Justify, Renderer, Text, UserInterface, }; use coffee::{Game, Result, Timer}; @@ -27,11 +27,7 @@ impl Game for ImageScreen { fn load(_window: &Window) -> Task { graphics::Image::load("resources/ui.png") - .map(|image| { - ImageScreen { - image, - } - }) + .map(|image| ImageScreen { image }) } fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { @@ -48,13 +44,12 @@ impl UserInterface for ImageScreen { type Message = (); type Renderer = Renderer; - fn react(&mut self, _message: ()) { - } + fn react(&mut self, _message: ()) {} fn layout(&mut self, window: &Window) -> Element<()> { Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .align_items(Align::Center) .justify_content(Justify::Center) .spacing(20) @@ -65,10 +60,7 @@ impl UserInterface for ImageScreen { .horizontal_alignment(HorizontalAlignment::Center) .vertical_alignment(VerticalAlignment::Center), ) - .push( - Image::new(&self.image) - .height(250) - ) + .push(Image::new(&self.image).height(250)) .into() } } diff --git a/examples/input.rs b/examples/input.rs index 49dc0bb..fbcd4e2 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -193,8 +193,8 @@ impl UserInterface for InputExample { )); Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .padding(20) .align_items(Align::Center) .justify_content(Justify::Center) diff --git a/examples/mesh.rs b/examples/mesh.rs index 91b6cd9..32ea177 100644 --- a/examples/mesh.rs +++ b/examples/mesh.rs @@ -90,20 +90,24 @@ impl Game for Example { }); let mut mesh = Mesh::new(); + let (x, y) = ( + f32::from(frame.width()) / 4.0, + f32::from(frame.height()) / 2.0, + ); let shape = match self.shape { ShapeOption::Rectangle => Shape::Rectangle(Rectangle { - x: frame.width() / 4.0 - 100.0, - y: frame.height() / 2.0 - 50.0, + x: x - 100.0, + y: y - 50.0, width: 200.0, height: 100.0, }), ShapeOption::Circle => Shape::Circle { - center: Point::new(frame.width() / 4.0, frame.height() / 2.0), + center: Point::new(x, y), radius: self.radius, }, ShapeOption::Ellipse => Shape::Ellipse { - center: Point::new(frame.width() / 4.0, frame.height() / 2.0), + center: Point::new(x, y), horizontal_radius: self.radius, vertical_radius: self.vertical_radius, rotation: 0.0, @@ -210,8 +214,8 @@ impl UserInterface for Example { controls.push(color_sliders(&mut self.color_sliders, self.color)); Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .padding(20) .align_items(Align::End) .justify_content(Justify::SpaceBetween) diff --git a/examples/particles.rs b/examples/particles.rs index 64a6435..b89f96d 100644 --- a/examples/particles.rs +++ b/examples/particles.rs @@ -40,7 +40,7 @@ impl Particles { const G: f32 = 6.674; const CENTER_MASS: f32 = 200.0; - fn generate(max_x: f32, max_y: f32) -> Task> { + fn generate(max_x: u16, max_y: u16) -> Task> { Task::succeed(move || { let rng = &mut rand::thread_rng(); @@ -179,8 +179,8 @@ impl UserInterface for Particles { Column::new() .padding(20) .spacing(20) - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .justify_content(Justify::End) .push(Checkbox::new( self.interpolate, @@ -204,11 +204,11 @@ struct Particle { } impl Particle { - fn random(max_x: f32, max_y: f32, rng: &mut R) -> Particle { + fn random(max_x: u16, max_y: u16, rng: &mut R) -> Particle { Particle { position: Point::new( - rng.gen_range(0.0, max_x), - rng.gen_range(0.0, max_y), + f32::from(rng.gen_range(0, max_x)), + f32::from(rng.gen_range(0, max_y)), ), velocity: Vector::new(0.0, 0.0), acceleration: Vector::new(0.0, 0.0), diff --git a/examples/progress.rs b/examples/progress.rs index c914fbd..430ab12 100644 --- a/examples/progress.rs +++ b/examples/progress.rs @@ -4,8 +4,7 @@ use coffee::graphics::{ }; use coffee::load::Task; use coffee::ui::{ - Align, Column, Element, Justify, Renderer, Text, - UserInterface, ProgressBar, + Align, Column, Element, Justify, ProgressBar, Renderer, Text, UserInterface, }; use coffee::{Game, Result, Timer}; @@ -27,9 +26,7 @@ impl Game for Progress { type LoadingScreen = (); fn load(_window: &Window) -> Task { - Task::succeed(|| Progress { - value: 0.0, - }) + Task::succeed(|| Progress { value: 0.0 }) } fn draw(&mut self, frame: &mut Frame, timer: &Timer) { @@ -53,13 +50,12 @@ impl UserInterface for Progress { type Message = (); type Renderer = Renderer; - fn react(&mut self, _message: ()) { - } + fn react(&mut self, _message: ()) {} fn layout(&mut self, window: &Window) -> Element<()> { Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .align_items(Align::Center) .justify_content(Justify::Center) .spacing(20) @@ -70,10 +66,7 @@ impl UserInterface for Progress { .horizontal_alignment(HorizontalAlignment::Center) .vertical_alignment(VerticalAlignment::Center), ) - .push( - ProgressBar::new(self.value) - .width(400), - ) + .push(ProgressBar::new(self.value).width(400)) .into() } } diff --git a/examples/ui.rs b/examples/ui.rs index 7582b45..79d2634 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -95,8 +95,8 @@ impl UserInterface for Tour { .push(controls); Column::new() - .width(window.width() as u32) - .height(window.height() as u32) + .width(window.width()) + .height(window.height()) .padding(20) .align_items(Align::Center) .justify_content(Justify::Center) diff --git a/src/graphics/canvas.rs b/src/graphics/canvas.rs index c59d971..ed7064b 100644 --- a/src/graphics/canvas.rs +++ b/src/graphics/canvas.rs @@ -55,8 +55,8 @@ impl Canvas { Target::with_transformation( gpu, self.drawable.target().clone(), - texture.width() as f32, - texture.height() as f32, + texture.width(), + texture.height(), texture::Drawable::render_transformation(), ) } diff --git a/src/graphics/rectangle.rs b/src/graphics/rectangle.rs index a24ccd4..de3aa47 100644 --- a/src/graphics/rectangle.rs +++ b/src/graphics/rectangle.rs @@ -33,9 +33,17 @@ impl Rectangle { /// [`Point`]: type.Point.html /// [`Rectangle`]: struct.Rectangle.html pub fn center(&self) -> Point { - Point::new( - self.x + self.width / 2.0, - self.y + self.height / 2.0, - ) + Point::new(self.x + self.width / 2.0, self.y + self.height / 2.0) + } +} + +impl From for Rectangle { + fn from(rectangle: iced::Rectangle) -> Rectangle { + Rectangle { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + } } } diff --git a/src/graphics/target.rs b/src/graphics/target.rs index a799934..482faf1 100644 --- a/src/graphics/target.rs +++ b/src/graphics/target.rs @@ -23,21 +23,24 @@ impl<'a> Target<'a> { pub(super) fn new( gpu: &mut Gpu, view: TargetView, - width: f32, - height: f32, + width: u16, + height: u16, ) -> Target<'_> { Target { gpu, view, - transformation: Transformation::orthographic(width, height), + transformation: Transformation::orthographic( + f32::from(width), + f32::from(height), + ), } } pub(super) fn with_transformation( gpu: &mut Gpu, view: TargetView, - width: f32, - height: f32, + width: u16, + height: u16, transformation: Transformation, ) -> Target<'_> { let mut target = Self::new(gpu, view, width, height); diff --git a/src/graphics/text.rs b/src/graphics/text.rs index 522ee86..2830a37 100644 --- a/src/graphics/text.rs +++ b/src/graphics/text.rs @@ -67,3 +67,59 @@ pub enum VerticalAlignment { /// Align bottom Bottom, } + +impl From for iced::text::HorizontalAlignment { + fn from( + horizontal_alignment: HorizontalAlignment, + ) -> iced::text::HorizontalAlignment { + match horizontal_alignment { + HorizontalAlignment::Left => iced::text::HorizontalAlignment::Left, + HorizontalAlignment::Center => { + iced::text::HorizontalAlignment::Center + } + HorizontalAlignment::Right => { + iced::text::HorizontalAlignment::Right + } + } + } +} + +impl From for HorizontalAlignment { + fn from( + horizontal_alignment: iced::text::HorizontalAlignment, + ) -> HorizontalAlignment { + match horizontal_alignment { + iced::text::HorizontalAlignment::Left => HorizontalAlignment::Left, + iced::text::HorizontalAlignment::Center => { + HorizontalAlignment::Center + } + iced::text::HorizontalAlignment::Right => { + HorizontalAlignment::Right + } + } + } +} + +impl From for iced::text::VerticalAlignment { + fn from( + vertical_alignment: VerticalAlignment, + ) -> iced::text::VerticalAlignment { + match vertical_alignment { + VerticalAlignment::Top => iced::text::VerticalAlignment::Top, + VerticalAlignment::Center => iced::text::VerticalAlignment::Center, + VerticalAlignment::Bottom => iced::text::VerticalAlignment::Bottom, + } + } +} + +impl From for VerticalAlignment { + fn from( + vertical_alignment: iced::text::VerticalAlignment, + ) -> VerticalAlignment { + match vertical_alignment { + iced::text::VerticalAlignment::Top => VerticalAlignment::Top, + iced::text::VerticalAlignment::Center => VerticalAlignment::Center, + iced::text::VerticalAlignment::Bottom => VerticalAlignment::Bottom, + } + } +} diff --git a/src/graphics/window.rs b/src/graphics/window.rs index 51aa2c1..81cd984 100644 --- a/src/graphics/window.rs +++ b/src/graphics/window.rs @@ -17,8 +17,8 @@ use crate::Result; pub struct Window { gpu: Gpu, surface: gpu::Surface, - width: f32, - height: f32, + width: u16, + height: u16, is_fullscreen: bool, } @@ -58,8 +58,8 @@ impl Window { is_fullscreen, gpu, surface, - width, - height, + width: width as u16, + height: height as u16, }) } @@ -96,14 +96,14 @@ impl Window { /// Returns the width of the [`Window`]. /// /// [`Window`]: struct.Window.html - pub fn width(&self) -> f32 { + pub fn width(&self) -> u16 { self.width } /// Returns the height of the [`Window`]. /// /// [`Window`]: struct.Window.html - pub fn height(&self) -> f32 { + pub fn height(&self) -> u16 { self.height } @@ -125,8 +125,8 @@ impl Window { self.surface.resize(&mut self.gpu, physical_size); - self.width = physical_size.width as f32; - self.height = physical_size.height as f32; + self.width = physical_size.width as u16; + self.height = physical_size.height as u16; } pub(crate) fn update_cursor( diff --git a/src/graphics/window/frame.rs b/src/graphics/window/frame.rs index 54e549c..5c4be45 100644 --- a/src/graphics/window/frame.rs +++ b/src/graphics/window/frame.rs @@ -33,12 +33,12 @@ impl<'a> Frame<'a> { } /// Get the width of the frame. - pub fn width(&self) -> f32 { + pub fn width(&self) -> u16 { self.window.width } /// Get the height of the frame. - pub fn height(&self) -> f32 { + pub fn height(&self) -> u16 { self.window.height } diff --git a/src/input/keyboard/event.rs b/src/input/keyboard/event.rs index 3804c42..061a128 100644 --- a/src/input/keyboard/event.rs +++ b/src/input/keyboard/event.rs @@ -19,3 +19,21 @@ pub enum Event { character: char, }, } + +#[doc(hidden)] +impl From for iced::input::keyboard::Event { + fn from(event: Event) -> iced::input::keyboard::Event { + match event { + Event::Input { state, key_code } => { + iced::input::keyboard::Event::Input { + state: state.into(), + key_code: key_code.into(), + } + } + + Event::TextEntered { character } => { + iced::input::keyboard::Event::CharacterReceived(character) + } + } + } +} diff --git a/src/input/mouse/event.rs b/src/input/mouse/event.rs index f26065a..823943b 100644 --- a/src/input/mouse/event.rs +++ b/src/input/mouse/event.rs @@ -48,3 +48,32 @@ pub enum Event { delta_y: f32, }, } + +#[doc(hidden)] +impl From for Option { + fn from(event: Event) -> Option { + match event { + Event::CursorEntered => { + Some(iced::input::mouse::Event::CursorEntered) + } + Event::CursorLeft => Some(iced::input::mouse::Event::CursorEntered), + Event::CursorMoved { x, y } => { + Some(iced::input::mouse::Event::CursorMoved { x, y }) + } + Event::Input { state, button } => { + Some(iced::input::mouse::Event::Input { + state: state.into(), + button: button.into(), + }) + } + Event::WheelScrolled { delta_x, delta_y } => { + Some(iced::input::mouse::Event::WheelScrolled { + delta_x, + delta_y, + }) + } + Event::CursorTaken => None, + Event::CursorReturned => None, + } + } +} diff --git a/src/load/loading_screen/progress_bar.rs b/src/load/loading_screen/progress_bar.rs index 1b0007d..bcbdb1b 100644 --- a/src/load/loading_screen/progress_bar.rs +++ b/src/load/loading_screen/progress_bar.rs @@ -38,10 +38,11 @@ impl LoadingScreen for ProgressBar { graphics::Quad { position: graphics::Point::new( 50.0, - frame.height() / 2.0 - 25.0, + f32::from(frame.height()) / 2.0 - 25.0, ), size: ( - (frame.width() - 100.0) * (progress.percentage() / 100.0), + (f32::from(frame.width()) - 100.0) + * (progress.percentage() / 100.0), 50.0, ), ..Default::default() @@ -54,7 +55,7 @@ impl LoadingScreen for ProgressBar { content: stage, position: graphics::Point::new( 50.0, - frame.height() / 2.0 - 80.0, + f32::from(frame.height()) / 2.0 - 80.0, ), size: 30.0, color: graphics::Color::WHITE, @@ -64,7 +65,10 @@ impl LoadingScreen for ProgressBar { self.font.add(graphics::Text { content: &(format!("{:.0}", progress.percentage()) + "%"), - position: graphics::Point::new(50.0, frame.height() / 2.0 + 50.0), + position: graphics::Point::new( + 50.0, + f32::from(frame.height()) / 2.0 + 50.0, + ), size: 30.0, color: graphics::Color::WHITE, ..graphics::Text::default() diff --git a/src/ui.rs b/src/ui.rs index 7bdfbe9..c598b23 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -144,6 +144,7 @@ pub mod widget; #[doc(no_inline)] pub use self::core::{Align, Justify}; pub use renderer::{Configuration, Renderer}; +#[doc(no_inline)] pub use widget::{ button, image, progress_bar, slider, Button, Checkbox, Image, ProgressBar, Radio, Slider, Text, @@ -168,10 +169,10 @@ pub type Row<'a, Message> = widget::Row<'a, Message, Renderer>; pub type Element<'a, Message> = self::core::Element<'a, Message, Renderer>; use crate::game::{self, Loop as _}; -use crate::graphics::{Point, Window, WindowSettings}; +use crate::graphics::{Window, WindowSettings}; use crate::input::{self, mouse, Input as _}; use crate::load::Task; -use crate::ui::core::{Event, Interface, MouseCursor, Renderer as _}; +use crate::ui::core::{MouseCursor, Renderer as _}; use crate::{Debug, Game, Result}; /// The user interface of your game. @@ -270,24 +271,19 @@ pub trait UserInterface: Game { struct Loop { renderer: UI::Renderer, - messages: Vec, mouse_cursor: MouseCursor, - cache: Option, - cursor_position: Point, - events: Vec, + cache: Option, + events: Vec, } impl game::Loop for Loop { type Attributes = UI::Renderer; - fn new(renderer: UI::Renderer, game: &mut UI, window: &Window) -> Self { - let cache = Interface::compute(game.layout(window), &renderer).cache(); + fn new(renderer: UI::Renderer, _game: &mut UI, _window: &Window) -> Self { Loop { renderer, - messages: Vec::new(), mouse_cursor: MouseCursor::OutOfBounds, - cache: Some(cache), - cursor_position: Point::new(0.0, 0.0), + cache: Some(iced::Cache::new()), events: Vec::new(), } } @@ -299,14 +295,7 @@ impl game::Loop for Loop { fn on_input(&mut self, input: &mut UI::Input, event: input::Event) { input.update(event); - match event { - input::Event::Mouse(mouse::Event::CursorMoved { x, y }) => { - self.cursor_position = Point::new(x, y); - } - _ => {} - }; - - if let Some(ui_event) = Event::from_input(event) { + if let Some(ui_event) = event_from_input(event) { self.events.push(ui_event); } } @@ -319,26 +308,18 @@ impl game::Loop for Loop { debug: &mut Debug, ) { debug.ui_started(); - let mut interface = Interface::compute_with_cache( + let mut interface = iced::UserInterface::build( ui.layout(window), - &self.renderer, self.cache.take().unwrap(), + &self.renderer, ); - let cursor_position = self.cursor_position; - let messages = &mut self.messages; - - self.events.drain(..).for_each(|event| { - interface.on_event(event, cursor_position, messages) - }); + let messages = interface.update(self.events.drain(..)); - let new_cursor = interface.draw( - &mut self.renderer, - &mut window.frame(), - cursor_position, - ); + let new_cursor = interface.draw(&mut self.renderer); + self.renderer.flush(&mut window.frame()); - self.cache = Some(interface.cache()); + self.cache = Some(interface.into_cache()); if new_cursor != self.mouse_cursor { if new_cursor == MouseCursor::OutOfBounds { @@ -351,9 +332,25 @@ impl game::Loop for Loop { self.mouse_cursor = new_cursor; } - for message in messages.drain(..) { + for message in messages { ui.react(message); } debug.ui_finished(); } } + +fn event_from_input(input_event: input::Event) -> Option { + match input_event { + input::Event::Keyboard(keyboard_event) => { + Some(iced::Event::Keyboard(keyboard_event.into())) + } + input::Event::Mouse(mouse_event) => { + let mouse_event: Option = + mouse_event.into(); + + mouse_event.map(iced::Event::Mouse) + } + input::Event::Gamepad { .. } => None, + input::Event::Window(..) => None, + } +} diff --git a/src/ui/core.rs b/src/ui/core.rs index b88c104..d96b97d 100644 --- a/src/ui/core.rs +++ b/src/ui/core.rs @@ -5,27 +5,12 @@ //! //! [`Widget`]: trait.Widget.html //! [`Renderer`]: trait.Renderer.html -mod element; -mod event; -mod hasher; -mod interface; -mod layout; -mod mouse_cursor; -mod node; mod renderer; -mod style; -mod widget; #[doc(no_inline)] -pub use stretch::{geometry::Size, number::Number}; +pub use iced::{ + renderer::Debugger, Align, Element, Event, Hasher, Justify, Layout, + MouseCursor, Node, Number, Size, Style, Widget, +}; -pub use element::Element; -pub use event::Event; -pub use hasher::Hasher; -pub(crate) use interface::{Cache, Interface}; -pub use layout::Layout; -pub use mouse_cursor::MouseCursor; -pub use node::Node; pub use renderer::Renderer; -pub use style::{Align, Justify, Style}; -pub use widget::Widget; diff --git a/src/ui/core/element.rs b/src/ui/core/element.rs deleted file mode 100644 index ab96d86..0000000 --- a/src/ui/core/element.rs +++ /dev/null @@ -1,299 +0,0 @@ -use stretch::{geometry, result}; - -use crate::graphics::{Color, Point}; -use crate::ui::core::{self, Event, Hasher, Layout, MouseCursor, Node, Widget}; - -/// A generic [`Widget`]. -/// -/// If you have a widget, you should be able to use `widget.into()` to turn it -/// into an [`Element`]. -/// -/// [`Widget`]: trait.Widget.html -/// [`Element`]: struct.Element.html -pub struct Element<'a, Message, Renderer> { - pub(crate) widget: Box + 'a>, -} - -impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Element") - .field("widget", &self.widget) - .finish() - } -} - -impl<'a, Message, Renderer> Element<'a, Message, Renderer> { - /// Create a new [`Element`] containing the given [`Widget`]. - /// - /// [`Element`]: struct.Element.html - /// [`Widget`]: trait.Widget.html - pub fn new( - widget: impl Widget + 'a, - ) -> Element<'a, Message, Renderer> { - Element { - widget: Box::new(widget), - } - } - - /// Applies a transformation to the produced message of the [`Element`]. - /// - /// This method is useful when you want to decouple different parts of your - /// UI. - /// - /// [`Element`]: struct.Element.html - /// - /// # Example - /// Let's say that we want to have a main menu and a gameplay overlay in our - /// game. We can decouple the interfaces nicely using modules and nested - /// messages: - /// - /// ``` - /// mod main_menu { - /// use coffee::ui::core::Element; - /// # use coffee::ui::Column; - /// use coffee::ui::Renderer; - /// - /// pub struct MainMenu { - /// // Our main menu state here... - /// // Probably a bunch of `button::State` and other stuff. - /// } - /// - /// #[derive(Debug, Clone, Copy)] - /// pub enum Message { - /// // The different interactions of the main menu here... - /// } - /// - /// impl MainMenu { - /// // We probably would have our `update` function here too... - /// - /// pub fn layout(&mut self) -> Element { - /// // We show the main menu here... - /// // The returned `Element` produces `main_menu::Message` - /// # Column::new().into() - /// } - /// } - /// } - /// - /// mod gameplay_overlay { - /// // Analogous to the `main_menu` module - /// # use coffee::ui::core::Element; - /// # use coffee::ui::Column; - /// # use coffee::ui::Renderer; - /// # - /// # pub struct GameplayOverlay { /* ... */ } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { /* ... */ } - /// # - /// # impl GameplayOverlay { - /// # pub fn layout(&mut self) -> Element { - /// # // ... - /// # Column::new().into() - /// # } - /// # } - /// } - /// - /// use coffee::ui::core::Element; - /// use coffee::ui::Renderer; - /// use main_menu::MainMenu; - /// use gameplay_overlay::GameplayOverlay; - /// - /// // The state of our UI - /// enum State { - /// MainMenu(MainMenu), - /// GameplayOverlay(GameplayOverlay), - /// // ... - /// } - /// - /// // The messages of our UI - /// // We nest the messages here - /// #[derive(Debug, Clone, Copy)] - /// enum Message { - /// MainMenu(main_menu::Message), - /// GameplayOverlay(gameplay_overlay::Message), - /// // ... - /// } - /// - /// // We show the UI here, transforming the local messages of each branch - /// // into the global `Message` type as needed. - /// pub fn layout(state: &mut State) -> Element { - /// match state { - /// State::MainMenu(main_menu) => { - /// main_menu.layout().map(Message::MainMenu) - /// } - /// State::GameplayOverlay(gameplay_overlay) => { - /// gameplay_overlay.layout().map(Message::GameplayOverlay) - /// } - /// // ... - /// } - /// } - /// ``` - /// - /// This way, neither `main_menu` nor `gameplay_overlay` know anything about - /// the global `Message` type. They become reusable, allowing the user of - /// these modules to compose them together freely. - pub fn map(self, f: F) -> Element<'a, B, Renderer> - where - Message: 'static + Copy, - Renderer: 'a, - B: 'static, - F: 'static + Fn(Message) -> B, - { - Element { - widget: Box::new(Map::new(self.widget, f)), - } - } - - /// Marks the [`Element`] as _to-be-explained_. - /// - /// The [`Renderer`] will explain the layout of the [`Element`] graphically. - /// This can be very useful for debugging your layout! - /// - /// [`Element`]: struct.Element.html - /// [`Renderer`]: trait.Renderer.html - pub fn explain(self, color: Color) -> Element<'a, Message, Renderer> - where - Message: 'static, - Renderer: 'a + core::Renderer, - { - Element { - widget: Box::new(Explain::new(self, color)), - } - } - - pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout { - let node = self.widget.node(renderer); - - node.0.compute_layout(geometry::Size::undefined()).unwrap() - } - - pub(crate) fn hash(&self, state: &mut Hasher) { - self.widget.hash(state); - } -} - -struct Map<'a, A, B, Renderer> { - widget: Box + 'a>, - mapper: Box B>, -} - -impl<'a, A, B, Renderer> std::fmt::Debug for Map<'a, A, B, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Map").field("widget", &self.widget).finish() - } -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new( - widget: Box + 'a>, - mapper: F, - ) -> Map<'a, A, B, Renderer> - where - F: 'static + Fn(A) -> B, - { - Map { - widget, - mapper: Box::new(mapper), - } - } -} - -impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> -where - A: Copy, -{ - fn node(&self, renderer: &Renderer) -> Node { - self.widget.node(renderer) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - let mut original_messages = Vec::new(); - - self.widget.on_event( - event, - layout, - cursor_position, - &mut original_messages, - ); - - original_messages - .iter() - .cloned() - .for_each(|message| messages.push((self.mapper)(message))); - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - self.widget.draw(renderer, layout, cursor_position) - } - - fn hash(&self, state: &mut Hasher) { - self.widget.hash(state); - } -} - -struct Explain<'a, Message, Renderer> { - element: Element<'a, Message, Renderer>, - color: Color, -} - -impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Explain") - .field("element", &self.element) - .finish() - } -} - -impl<'a, Message, Renderer> Explain<'a, Message, Renderer> { - fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self { - Explain { element, color } - } -} - -impl<'a, Message, Renderer> Widget - for Explain<'a, Message, Renderer> -where - Renderer: core::Renderer, -{ - fn node(&self, renderer: &Renderer) -> Node { - self.element.widget.node(renderer) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - self.element - .widget - .on_event(event, layout, cursor_position, messages) - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - renderer.explain(&layout, self.color); - - self.element.widget.draw(renderer, layout, cursor_position) - } - - fn hash(&self, state: &mut Hasher) { - self.element.widget.hash(state); - } -} diff --git a/src/ui/core/event.rs b/src/ui/core/event.rs deleted file mode 100644 index 836aab6..0000000 --- a/src/ui/core/event.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::input::{self, gamepad, keyboard, mouse}; - -/// A user interface event. -/// -/// This is a subset of [`input::Event`]. -/// -/// [`input::Event`]: ../../input/enum.Event.html -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum Event { - /// A keyboard event - Keyboard(keyboard::Event), - - /// A mouse event - Mouse(mouse::Event), - - /// A gamepad event - Gamepad { - /// The gamepad identifier - id: gamepad::Id, - - /// The gamepad event - event: gamepad::Event, - }, -} - -impl Event { - pub(crate) fn from_input(event: input::Event) -> Option { - match event { - input::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - input::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - input::Event::Gamepad { id, event, .. } => { - Some(Event::Gamepad { id, event }) - } - _ => None, - } - } -} diff --git a/src/ui/core/hasher.rs b/src/ui/core/hasher.rs deleted file mode 100644 index a930fa1..0000000 --- a/src/ui/core/hasher.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// The hasher used to compare layouts. -pub type Hasher = twox_hash::XxHash; diff --git a/src/ui/core/interface.rs b/src/ui/core/interface.rs deleted file mode 100644 index 6c66b8f..0000000 --- a/src/ui/core/interface.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::hash::Hasher; -use stretch::result; - -use crate::graphics::{Frame, Point}; -use crate::ui::core::{self, Element, Event, Layout, MouseCursor}; - -pub struct Interface<'a, Message, Renderer> { - hash: u64, - root: Element<'a, Message, Renderer>, - layout: result::Layout, -} - -pub struct Cache { - hash: u64, - layout: result::Layout, -} - -impl<'a, Message, Renderer> Interface<'a, Message, Renderer> -where - Renderer: core::Renderer, -{ - pub fn compute( - root: Element<'a, Message, Renderer>, - renderer: &Renderer, - ) -> Interface<'a, Message, Renderer> { - let hasher = &mut twox_hash::XxHash::default(); - root.hash(hasher); - - let hash = hasher.finish(); - let layout = root.compute_layout(renderer); - - Interface { hash, root, layout } - } - - pub fn compute_with_cache( - root: Element<'a, Message, Renderer>, - renderer: &Renderer, - cache: Cache, - ) -> Interface<'a, Message, Renderer> { - let hasher = &mut twox_hash::XxHash::default(); - root.hash(hasher); - - let hash = hasher.finish(); - - let layout = if hash == cache.hash { - cache.layout - } else { - root.compute_layout(renderer) - }; - - Interface { hash, root, layout } - } - - pub fn on_event( - &mut self, - event: Event, - cursor_position: Point, - messages: &mut Vec, - ) { - let Interface { root, layout, .. } = self; - - root.widget.on_event( - event, - Self::layout(layout), - cursor_position, - messages, - ); - } - - pub fn draw( - &self, - renderer: &mut Renderer, - frame: &mut Frame<'_>, - cursor_position: Point, - ) -> MouseCursor { - let Interface { root, layout, .. } = self; - - let cursor = - root.widget - .draw(renderer, Self::layout(layout), cursor_position); - - renderer.flush(frame); - - cursor - } - - pub fn cache(self) -> Cache { - Cache { - hash: self.hash, - layout: self.layout, - } - } - - fn layout(layout: &result::Layout) -> Layout<'_> { - Layout::new(layout, Point::new(0.0, 0.0)) - } -} diff --git a/src/ui/core/layout.rs b/src/ui/core/layout.rs deleted file mode 100644 index e9fa6d8..0000000 --- a/src/ui/core/layout.rs +++ /dev/null @@ -1,59 +0,0 @@ -use stretch::result; - -use crate::graphics::{Point, Rectangle, Vector}; - -/// The computed bounds of a [`Node`] and its children. -/// -/// This type is provided by the GUI runtime to [`Widget::on_event`] and -/// [`Widget::draw`], describing the layout of the produced [`Node`] by -/// [`Widget::node`]. -/// -/// [`Node`]: struct.Node.html -/// [`Widget::on_event`]: trait.Widget.html#method.on_event -/// [`Widget::draw`]: trait.Widget.html#tymethod.draw -/// [`Widget::node`]: trait.Widget.html#tymethod.node -#[derive(Debug)] -pub struct Layout<'a> { - layout: &'a result::Layout, - position: Point, -} - -impl<'a> Layout<'a> { - pub(crate) fn new( - layout: &'a result::Layout, - parent_position: Point, - ) -> Self { - let position = - parent_position + Vector::new(layout.location.x, layout.location.y); - - Layout { layout, position } - } - - /// Gets the bounds of the [`Layout`]. - /// - /// The returned [`Rectangle`] describes the position and size of a - /// [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Rectangle`]: ../../graphics/struct.Rectangle.html - /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { - Rectangle { - x: self.position.x, - y: self.position.y, - width: self.layout.size.width, - height: self.layout.size.height, - } - } - - /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Node`]: struct.Node.html - pub fn children(&'a self) -> impl Iterator> { - self.layout - .children - .iter() - .map(move |layout| Layout::new(layout, self.position)) - } -} diff --git a/src/ui/core/mouse_cursor.rs b/src/ui/core/mouse_cursor.rs deleted file mode 100644 index 4b99ee6..0000000 --- a/src/ui/core/mouse_cursor.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::graphics::window::winit; - -/// The state of the mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum MouseCursor { - /// The cursor is out of the bounds of the user interface. - OutOfBounds, - - /// The cursor is over a non-interactive widget. - Idle, - - /// The cursor is over a clickable widget. - Pointer, - - /// The cursor is over a busy widget. - Working, - - /// The cursor is over a grabbable widget. - Grab, - - /// The cursor is grabbing a widget. - Grabbing, -} - -#[doc(hidden)] -impl From for winit::window::CursorIcon { - fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - } - } -} diff --git a/src/ui/core/node.rs b/src/ui/core/node.rs deleted file mode 100644 index 56a445a..0000000 --- a/src/ui/core/node.rs +++ /dev/null @@ -1,60 +0,0 @@ -use stretch::node; - -use crate::ui::core::{Number, Size, Style}; - -/// The visual requirements of a [`Widget`] and its children. -/// -/// When there have been changes and the [`Layout`] needs to be recomputed, the -/// runtime obtains a [`Node`] by calling [`Widget::node`]. -/// -/// [`Style`]: struct.Style.html -/// [`Widget`]: trait.Widget.html -/// [`Node`]: struct.Node.html -/// [`Widget::node`]: trait.Widget.html#tymethod.node -/// [`Layout`]: struct.Layout.html -#[derive(Debug)] -pub struct Node(pub(crate) node::Node); - -impl Node { - /// Creates a new [`Node`] with the given [`Style`]. - /// - /// [`Node`]: struct.Node.html - /// [`Style`]: struct.Style.html - pub fn new(style: Style) -> Node { - Self::with_children(style, Vec::new()) - } - - /// Creates a new [`Node`] with the given [`Style`] and children. - /// - /// [`Node`]: struct.Node.html - /// [`Style`]: struct.Style.html - pub(crate) fn with_children(style: Style, children: Vec) -> Node { - Node(node::Node::new( - style.0, - children.iter().map(|c| &c.0).collect(), - )) - } - - /// Creates a new [`Node`] with the given [`Style`] and a measure function. - /// - /// This type of node cannot have any children. - /// - /// You should use this when your [`Widget`] can adapt its contents to the - /// size of its container. The measure function will receive the container - /// size as a parameter and must compute the size of the [`Node`] inside - /// the given bounds (if the `Number` for a dimension is `Undefined` it - /// means that it has no boundary). - /// - /// [`Node`]: struct.Node.html - /// [`Style`]: struct.Style.html - /// [`Widget`]: trait.Widget.html - pub fn with_measure(style: Style, measure: F) -> Node - where - F: 'static + Fn(Size) -> Size, - { - Node(node::Node::new_leaf( - style.0, - Box::new(move |size| Ok(measure(size))), - )) - } -} diff --git a/src/ui/core/renderer.rs b/src/ui/core/renderer.rs index 2dfea5e..31c1f1d 100644 --- a/src/ui/core/renderer.rs +++ b/src/ui/core/renderer.rs @@ -1,6 +1,5 @@ -use crate::graphics::{Color, Frame}; +use crate::graphics::Frame; use crate::load::Task; -use crate::ui::core::Layout; /// The renderer of a user interface. /// @@ -23,16 +22,6 @@ pub trait Renderer { where Self: Sized; - /// Explains the [`Layout`] of an [`Element`] for debugging purposes. - /// - /// This will be called when [`Element::explain`] has been used. It should - /// _explain_ the [`Layout`] graphically. - /// - /// [`Layout`]: struct.Layout.html - /// [`Element`]: struct.Element.html - /// [`Element::explain`]: struct.Element.html#method.explain - fn explain(&mut self, layout: &Layout<'_>, color: Color); - /// Flushes the renderer to draw on the given [`Frame`]. /// /// This method will be called by the runtime after calling [`Widget::draw`] diff --git a/src/ui/core/style.rs b/src/ui/core/style.rs deleted file mode 100644 index 9e1f7df..0000000 --- a/src/ui/core/style.rs +++ /dev/null @@ -1,260 +0,0 @@ -use std::hash::{Hash, Hasher}; -use stretch::{geometry, style}; - -/// The appearance of a [`Node`]. -/// -/// [`Node`]: struct.Node.html -#[derive(Debug, Clone, Copy)] -pub struct Style(pub(crate) style::Style); - -impl Style { - /// Defines the width of a [`Node`] in pixels. - /// - /// [`Node`]: struct.Node.html - pub fn width(mut self, width: u32) -> Self { - self.0.size.width = style::Dimension::Points(width as f32); - self - } - - /// Defines the height of a [`Node`] in pixels. - /// - /// [`Node`]: struct.Node.html - pub fn height(mut self, height: u32) -> Self { - self.0.size.height = style::Dimension::Points(height as f32); - self - } - - /// Defines the minimum width of a [`Node`] in pixels. - /// - /// [`Node`]: struct.Node.html - pub fn min_width(mut self, min_width: u32) -> Self { - self.0.min_size.width = style::Dimension::Points(min_width as f32); - self - } - - /// Defines the maximum width of a [`Node`] in pixels. - /// - /// [`Node`]: struct.Node.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.0.max_size.width = style::Dimension::Points(max_width as f32); - self.fill_width() - } - - /// 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); - 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); - self.fill_height() - } - - /// Makes a [`Node`] fill all the horizontal available space. - /// - /// [`Node`]: struct.Node.html - pub fn fill_width(mut self) -> Self { - self.0.size.width = stretch::style::Dimension::Percent(1.0); - self - } - - /// Makes a [`Node`] fill all the vertical available space. - /// - /// [`Node`]: struct.Node.html - pub fn fill_height(mut self) -> Self { - self.0.size.height = stretch::style::Dimension::Percent(1.0); - self - } - - pub(crate) fn align_items(mut self, align: Align) -> Self { - self.0.align_items = align.into(); - self - } - - pub(crate) fn justify_content(mut self, justify: Justify) -> Self { - self.0.justify_content = justify.into(); - self - } - - /// Sets the alignment of a [`Node`]. - /// - /// If the [`Node`] is inside a... - /// - /// * [`Column`], this setting will affect its __horizontal__ alignment. - /// * [`Row`], this setting will affect its __vertical__ alignment. - /// - /// [`Node`]: struct.Node.html - /// [`Column`]: widget/struct.Column.html - /// [`Row`]: widget/struct.Row.html - pub fn align_self(mut self, align: Align) -> Self { - self.0.align_self = align.into(); - self - } - - /// Sets the padding of a [`Node`] in pixels. - /// - /// [`Node`]: struct.Node.html - pub fn padding(mut self, px: u32) -> Self { - self.0.padding = stretch::geometry::Rect { - start: style::Dimension::Points(px as f32), - end: style::Dimension::Points(px as f32), - top: style::Dimension::Points(px as f32), - bottom: style::Dimension::Points(px as f32), - }; - - self - } -} - -impl Default for Style { - fn default() -> Style { - Style(style::Style { - align_items: style::AlignItems::FlexStart, - justify_content: style::JustifyContent::FlexStart, - ..style::Style::default() - }) - } -} - -impl Hash for Style { - fn hash(&self, state: &mut H) { - hash_size(&self.0.size, state); - hash_size(&self.0.min_size, state); - hash_size(&self.0.max_size, state); - - hash_rect(&self.0.margin, state); - - (self.0.flex_direction as u8).hash(state); - (self.0.align_items as u8).hash(state); - (self.0.justify_content as u8).hash(state); - (self.0.align_self as u8).hash(state); - (self.0.flex_grow as u32).hash(state); - } -} - -fn hash_size( - size: &geometry::Size, - state: &mut H, -) { - hash_dimension(size.width, state); - hash_dimension(size.height, state); -} - -fn hash_rect( - rect: &geometry::Rect, - state: &mut H, -) { - hash_dimension(rect.start, state); - hash_dimension(rect.end, state); - hash_dimension(rect.top, state); - hash_dimension(rect.bottom, state); -} - -fn hash_dimension(dimension: style::Dimension, state: &mut H) { - match dimension { - style::Dimension::Undefined => state.write_u8(0), - style::Dimension::Auto => state.write_u8(1), - style::Dimension::Points(points) => { - state.write_u8(2); - (points as u32).hash(state); - } - style::Dimension::Percent(percent) => { - state.write_u8(3); - (percent as u32).hash(state); - } - } -} - -/// Alignment on the cross axis of a container. -/// -/// * On a [`Column`], it describes __horizontal__ alignment. -/// * On a [`Row`], it describes __vertical__ alignment. -/// -/// [`Column`]: widget/struct.Column.html -/// [`Row`]: widget/struct.Row.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Align { - /// Align at the start of the cross axis. - Start, - - /// Align at the center of the cross axis. - Center, - - /// Align at the end of the cross axis. - End, - - /// Stretch over the cross axis. - Stretch, -} - -#[doc(hidden)] -impl From for style::AlignItems { - fn from(align: Align) -> Self { - match align { - Align::Start => style::AlignItems::FlexStart, - Align::Center => style::AlignItems::Center, - Align::End => style::AlignItems::FlexEnd, - Align::Stretch => style::AlignItems::Stretch, - } - } -} - -#[doc(hidden)] -impl From for style::AlignSelf { - fn from(align: Align) -> Self { - match align { - Align::Start => style::AlignSelf::FlexStart, - Align::Center => style::AlignSelf::Center, - Align::End => style::AlignSelf::FlexEnd, - Align::Stretch => style::AlignSelf::Stretch, - } - } -} - -/// Distribution on the main axis of a container. -/// -/// * On a [`Column`], it describes __vertical__ distribution. -/// * On a [`Row`], it describes __horizontal__ distribution. -/// -/// [`Column`]: widget/struct.Column.html -/// [`Row`]: widget/struct.Row.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Justify { - /// Place items at the start of the main axis. - Start, - - /// Place items at the center of the main axis. - Center, - - /// Place items at the end of the main axis. - End, - - /// Place items with space between. - SpaceBetween, - - /// Place items with space around. - SpaceAround, - - /// Place items with evenly distributed space. - SpaceEvenly, -} - -#[doc(hidden)] -impl From for style::JustifyContent { - fn from(justify: Justify) -> Self { - match justify { - Justify::Start => style::JustifyContent::FlexStart, - Justify::Center => style::JustifyContent::Center, - Justify::End => style::JustifyContent::FlexEnd, - Justify::SpaceBetween => style::JustifyContent::SpaceBetween, - Justify::SpaceAround => style::JustifyContent::SpaceAround, - Justify::SpaceEvenly => style::JustifyContent::SpaceEvenly, - } - } -} diff --git a/src/ui/core/widget.rs b/src/ui/core/widget.rs deleted file mode 100644 index 93be8e0..0000000 --- a/src/ui/core/widget.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::graphics::Point; -use crate::ui::core::{Event, Hasher, Layout, MouseCursor, Node}; - -/// A component that displays information or allows interaction. -/// -/// If you want to build a custom widget, you will need to implement this trait. -/// Additionally, remember to also provide [`Into`] so your users can -/// easily turn your [`Widget`] into a generic [`Element`] -/// -/// [`Into`]: struct.Element.html -/// [`Widget`]: trait.Widget.html -/// [`Element`]: struct.Element.html -pub trait Widget: std::fmt::Debug { - /// Returns the [`Node`] of the [`Widget`]. - /// - /// This [`Node`] is used by the runtime to compute the [`Layout`] of the - /// user interface. - /// - /// [`Node`]: struct.Node.html - /// [`Widget`]: trait.Widget.html - /// [`Layout`]: struct.Layout.html - fn node(&self, renderer: &Renderer) -> Node; - - /// Draws the [`Widget`] using the associated `Renderer`. - /// - /// It must return the [`MouseCursor`] state for the [`Widget`]. - /// - /// [`Widget`]: trait.Widget.html - /// [`MouseCursor`]: enum.MouseCursor.html - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor; - - /// Computes the _layout_ hash of the [`Widget`]. - /// - /// The produced hash is used by the runtime to decide if the [`Layout`] - /// needs to be recomputed between frames. Therefore, to ensure maximum - /// efficiency, the hash should only be affected by the properties of the - /// [`Widget`] that can affect layouting. - /// - /// For example, the [`Text`] widget does not hash its color property, as - /// its value cannot affect the overall [`Layout`] of the user interface. - /// - /// [`Widget`]: trait.Widget.html - /// [`Layout`]: struct.Layout.html - /// [`Text`]: ../widget/text/struct.Text.html - fn hash(&self, state: &mut Hasher); - - /// Processes a runtime [`Event`]. - /// - /// It receives: - /// * an [`Event`] describing user interaction - /// * the computed [`Layout`] of the [`Widget`] - /// * the current cursor position - /// * a mutable `Message` vector, allowing the [`Widget`] to produce - /// new messages based on user interaction. - /// - /// By default, it does nothing. - /// - /// [`Event`]: enum.Event.html - /// [`Widget`]: trait.Widget.html - /// [`Layout`]: struct.Layout.html - fn on_event( - &mut self, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _messages: &mut Vec, - ) { - } -} diff --git a/src/ui/renderer.rs b/src/ui/renderer.rs index 6dc3ecf..e43a592 100644 --- a/src/ui/renderer.rs +++ b/src/ui/renderer.rs @@ -51,15 +51,6 @@ impl core::Renderer for Renderer { }) } - fn explain(&mut self, layout: &core::Layout<'_>, color: Color) { - self.explain_mesh - .stroke(Shape::Rectangle(layout.bounds()), color, 1.0); - - layout - .children() - .for_each(|layout| self.explain(&layout, color)); - } - fn flush(&mut self, frame: &mut Frame<'_>) { let target = &mut frame.as_target(); @@ -81,6 +72,22 @@ impl core::Renderer for Renderer { } } +impl iced::renderer::Debugger for Renderer { + type Color = Color; + + fn explain(&mut self, layout: &core::Layout<'_>, color: Color) { + self.explain_mesh.stroke( + Shape::Rectangle(layout.bounds().into()), + color, + 1.0, + ); + + layout + .children() + .for_each(|layout| self.explain(&layout, color)); + } +} + /// The [`Renderer`] configuration. /// /// You can implement [`UserInterface::configuration`] and return your own diff --git a/src/ui/renderer/button.rs b/src/ui/renderer/button.rs index 4feed4c..48b6f79 100644 --- a/src/ui/renderer/button.rs +++ b/src/ui/renderer/button.rs @@ -29,8 +29,8 @@ const RIGHT: Rectangle = Rectangle { impl button::Renderer for Renderer { fn draw( &mut self, - cursor_position: Point, - mut bounds: Rectangle, + cursor_position: iced::Point, + mut bounds: iced::Rectangle, state: &button::State, label: &str, class: button::Class, diff --git a/src/ui/renderer/checkbox.rs b/src/ui/renderer/checkbox.rs index 0b99a57..e14e079 100644 --- a/src/ui/renderer/checkbox.rs +++ b/src/ui/renderer/checkbox.rs @@ -1,8 +1,9 @@ use crate::graphics::{Point, Rectangle, Sprite}; use crate::ui::core::MouseCursor; -use crate::ui::widget::checkbox; use crate::ui::Renderer; +use iced::widget::checkbox; + const SPRITE: Rectangle = Rectangle { x: 98, y: 0, @@ -13,9 +14,9 @@ const SPRITE: Rectangle = Rectangle { impl checkbox::Renderer for Renderer { fn draw( &mut self, - cursor_position: Point, - bounds: Rectangle, - text_bounds: Rectangle, + cursor_position: iced::Point, + bounds: iced::Rectangle, + text_bounds: iced::Rectangle, is_checked: bool, ) -> MouseCursor { let mouse_over = bounds.contains(cursor_position) diff --git a/src/ui/renderer/radio.rs b/src/ui/renderer/radio.rs index 626a7b2..821d43c 100644 --- a/src/ui/renderer/radio.rs +++ b/src/ui/renderer/radio.rs @@ -1,8 +1,9 @@ use crate::graphics::{Point, Rectangle, Sprite}; use crate::ui::core::MouseCursor; -use crate::ui::widget::radio; use crate::ui::Renderer; +use iced::widget::radio; + const SPRITE: Rectangle = Rectangle { x: 98, y: 28, @@ -13,9 +14,9 @@ const SPRITE: Rectangle = Rectangle { impl radio::Renderer for Renderer { fn draw( &mut self, - cursor_position: Point, - bounds: Rectangle, - bounds_with_label: Rectangle, + cursor_position: iced::Point, + bounds: iced::Rectangle, + bounds_with_label: iced::Rectangle, is_selected: bool, ) -> MouseCursor { let mouse_over = bounds_with_label.contains(cursor_position); diff --git a/src/ui/renderer/slider.rs b/src/ui/renderer/slider.rs index 9a7a00f..397dfc5 100644 --- a/src/ui/renderer/slider.rs +++ b/src/ui/renderer/slider.rs @@ -1,7 +1,8 @@ use crate::graphics::{Point, Rectangle, Sprite}; use crate::ui::core::MouseCursor; -use crate::ui::{slider, Renderer}; +use crate::ui::Renderer; +use iced::slider; use std::ops::RangeInclusive; const RAIL: Rectangle = Rectangle { @@ -21,8 +22,8 @@ const MARKER: Rectangle = Rectangle { impl slider::Renderer for Renderer { fn draw( &mut self, - cursor_position: Point, - bounds: Rectangle, + cursor_position: iced::Point, + bounds: iced::Rectangle, state: &slider::State, range: RangeInclusive, value: f32, diff --git a/src/ui/renderer/text.rs b/src/ui/renderer/text.rs index 43afba2..224e3ea 100644 --- a/src/ui/renderer/text.rs +++ b/src/ui/renderer/text.rs @@ -1,17 +1,16 @@ -use crate::graphics::{ - self, Color, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; +use crate::graphics::{self, Color, Point}; use crate::ui::core::{Node, Number, Size, Style}; -use crate::ui::widget::text; use crate::ui::Renderer; +use iced::text; use std::cell::RefCell; use std::f32; -impl text::Renderer for Renderer { - fn node(&self, style: Style, content: &str, size: f32) -> Node { +impl text::Renderer for Renderer { + fn node(&self, style: Style, content: &str, size: Option) -> Node { let font = self.font.clone(); let content = String::from(content); + let size = size.unwrap_or(20); let measure = RefCell::new(None); Node::with_measure(style, move |bounds| { @@ -38,7 +37,7 @@ impl text::Renderer for Renderer { let text = graphics::Text { content: &content, - size, + size: f32::from(size), bounds, ..graphics::Text::default() }; @@ -62,21 +61,21 @@ impl text::Renderer for Renderer { fn draw( &mut self, - bounds: Rectangle, + bounds: iced::Rectangle, content: &str, - size: f32, - color: Color, - horizontal_alignment: HorizontalAlignment, - vertical_alignment: VerticalAlignment, + size: Option, + color: Option, + horizontal_alignment: text::HorizontalAlignment, + vertical_alignment: text::VerticalAlignment, ) { self.font.borrow_mut().add(graphics::Text { content, position: Point::new(bounds.x, bounds.y), bounds: (bounds.width, bounds.height), - color, - size, - horizontal_alignment, - vertical_alignment, + color: color.unwrap_or(Color::WHITE), + size: f32::from(size.unwrap_or(20)), + horizontal_alignment: horizontal_alignment.into(), + vertical_alignment: vertical_alignment.into(), }); } } diff --git a/src/ui/widget.rs b/src/ui/widget.rs index 45489b7..3b92a3e 100644 --- a/src/ui/widget.rs +++ b/src/ui/widget.rs @@ -22,23 +22,23 @@ //! [`Row`]: struct.Row.html //! [`Column`]: struct.Column.html //! [`Renderer`]: ../struct.Renderer.html -mod column; -mod row; -pub mod button; -pub mod checkbox; +use crate::graphics::Color; + +mod text; + pub mod image; pub mod progress_bar; -pub mod radio; -pub mod slider; -pub mod text; -pub use button::Button; -pub use checkbox::Checkbox; -pub use column::Column; pub use self::image::Image; pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use row::Row; -pub use slider::Slider; pub use text::Text; + +#[doc(no_inline)] +pub use iced::{button, slider, Button, Column, Row, Slider}; + +/// A checkbox. +pub type Checkbox = iced::Checkbox; + +/// A radio button. +pub type Radio = iced::Radio; diff --git a/src/ui/widget/button.rs b/src/ui/widget/button.rs deleted file mode 100644 index 7607924..0000000 --- a/src/ui/widget/button.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`] and a [`Class`]. -//! -//! [`Button`]: struct.Button.html -//! [`State`]: struct.State.html -//! [`Class`]: enum.Class.html - -use crate::graphics::{Point, Rectangle}; -use crate::input::{mouse, ButtonState}; -use crate::ui::core::{ - Align, Element, Event, Hasher, Layout, MouseCursor, Node, Style, Widget, -}; - -use std::hash::Hash; - -/// A generic widget that produces a message when clicked. -/// -/// It implements [`Widget`] when the associated [`core::Renderer`] implements -/// the [`button::Renderer`] trait. -/// -/// [`Widget`]: ../../core/trait.Widget.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -/// [`button::Renderer`]: trait.Renderer.html -/// -/// # Example -/// -/// ``` -/// use coffee::ui::{button, Button}; -/// -/// pub enum Message { -/// ButtonClicked, -/// } -/// -/// let state = &mut button::State::new(); -/// -/// Button::new(state, "Click me!") -/// .on_press(Message::ButtonClicked); -/// ``` -/// -/// ![Button drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button.png?raw=true) -pub struct Button<'a, Message> { - state: &'a mut State, - label: String, - class: Class, - on_press: Option, - style: Style, -} - -impl<'a, Message> std::fmt::Debug for Button<'a, Message> -where - Message: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Button") - .field("state", &self.state) - .field("label", &self.label) - .field("class", &self.class) - .field("on_press", &self.on_press) - .field("style", &self.style) - .finish() - } -} - -impl<'a, Message> Button<'a, Message> { - /// Creates a new [`Button`] with some local [`State`] and the given label. - /// - /// The default [`Class`] of a new [`Button`] is [`Class::Primary`]. - /// - /// [`Button`]: struct.Button.html - /// [`State`]: struct.State.html - /// [`Class`]: enum.Class.html - /// [`Class::Primary`]: enum.Class.html#variant.Primary - pub fn new(state: &'a mut State, label: &str) -> Self { - Button { - state, - label: String::from(label), - class: Class::Primary, - on_press: None, - style: Style::default().min_width(100), - } - } - - /// Sets the width of the [`Button`] in pixels. - /// - /// [`Button`]: struct.Button.html - pub fn width(mut self, width: u32) -> Self { - self.style = self.style.width(width); - self - } - - /// Makes the [`Button`] fill the horizontal space of its container. - /// - /// [`Button`]: struct.Button.html - pub fn fill_width(mut self) -> Self { - self.style = self.style.fill_width(); - self - } - - /// Sets the alignment of the [`Button`] itself. - /// - /// This is useful if you want to override the default alignment given by - /// the parent container. - /// - /// [`Button`]: struct.Button.html - pub fn align_self(mut self, align: Align) -> Self { - self.style = self.style.align_self(align); - self - } - - /// Sets the [`Class`] of the [`Button`]. - /// - /// - /// [`Button`]: struct.Button.html - /// [`Class`]: enum.Class.html - pub fn class(mut self, class: Class) -> Self { - self.class = class; - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// - /// [`Button`]: struct.Button.html - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } -} - -impl<'a, Message, Renderer> Widget for Button<'a, Message> -where - Renderer: self::Renderer, - Message: Copy + std::fmt::Debug, -{ - fn node(&self, _renderer: &Renderer) -> Node { - Node::new(self.style.height(50)) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, - }) => { - if let Some(on_press) = self.on_press { - let bounds = layout.bounds(); - - match state { - ButtonState::Pressed => { - self.state.is_pressed = - bounds.contains(cursor_position); - } - ButtonState::Released => { - let is_clicked = self.state.is_pressed - && bounds.contains(cursor_position); - - self.state.is_pressed = false; - - if is_clicked { - messages.push(on_press); - } - } - } - } - } - _ => {} - } - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - renderer.draw( - cursor_position, - layout.bounds(), - self.state, - &self.label, - self.class, - ) - } - - fn hash(&self, state: &mut Hasher) { - self.style.hash(state); - } -} - -/// The local state of a [`Button`]. -/// -/// [`Button`]: struct.Button.html -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_pressed: bool, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() - } - - /// Returns whether the associated [`Button`] is currently being pressed or - /// not. - /// - /// [`Button`]: struct.Button.html - pub fn is_pressed(&self) -> bool { - self.is_pressed - } -} - -/// The type of a [`Button`]. -/// -/// ![Different buttons drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button_classes.png?raw=true) -/// -/// [`Button`]: struct.Button.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Class { - /// The [`Button`] performs the main action. - /// - /// [`Button`]: struct.Button.html - Primary, - - /// The [`Button`] performs an alternative action. - /// - /// [`Button`]: struct.Button.html - Secondary, - - /// The [`Button`] performs a productive action. - /// - /// [`Button`]: struct.Button.html - Positive, -} - -/// The renderer of a [`Button`]. -/// -/// Your [`core::Renderer`] will need to implement this trait before being -/// able to use a [`Button`] in your user interface. -/// -/// [`Button`]: struct.Button.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -pub trait Renderer { - /// Draws a [`Button`]. - /// - /// It receives: - /// * the current cursor position - /// * the bounds of the [`Button`] - /// * the local state of the [`Button`] - /// * the label of the [`Button`] - /// * the [`Class`] of the [`Button`] - /// - /// [`Button`]: struct.Button.html - /// [`State`]: struct.State.html - /// [`Class`]: enum.Class.html - fn draw( - &mut self, - cursor_position: Point, - bounds: Rectangle, - state: &State, - label: &str, - class: Class, - ) -> MouseCursor; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: self::Renderer, - Message: 'static + Copy + std::fmt::Debug, -{ - fn from(button: Button<'a, Message>) -> Element<'a, Message, Renderer> { - Element::new(button) - } -} diff --git a/src/ui/widget/checkbox.rs b/src/ui/widget/checkbox.rs deleted file mode 100644 index e856c19..0000000 --- a/src/ui/widget/checkbox.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Show toggle controls using checkboxes. -use std::hash::Hash; - -use crate::graphics::{ - Color, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; -use crate::input::{mouse, ButtonState}; -use crate::ui::core::{ - Align, Element, Event, Hasher, Layout, MouseCursor, Node, Widget, -}; -use crate::ui::widget::{text, Column, Row, Text}; - -/// A box that can be checked. -/// -/// It implements [`Widget`] when the [`core::Renderer`] implements the -/// [`checkbox::Renderer`] trait. -/// -/// [`Widget`]: ../../core/trait.Widget.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -/// [`checkbox::Renderer`]: trait.Renderer.html -/// -/// # Example -/// -/// ``` -/// use coffee::graphics::Color; -/// use coffee::ui::Checkbox; -/// -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled) -/// .label_color(Color::BLACK); -/// ``` -/// -/// ![Checkbox drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) -pub struct Checkbox { - is_checked: bool, - on_toggle: Box Message>, - label: String, - label_color: Color, -} - -impl std::fmt::Debug for Checkbox { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Checkbox") - .field("is_checked", &self.is_checked) - .field("label", &self.label) - .field("label_color", &self.label_color) - .finish() - } -} - -impl Checkbox { - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. - /// It receives the new state of the [`Checkbox`] and must produce a - /// `Message`. - /// - /// [`Checkbox`]: struct.Checkbox.html - pub fn new(is_checked: bool, label: &str, f: F) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Box::new(f), - label: String::from(label), - label_color: Color::WHITE, - } - } - - /// Sets the [`Color`] of the label of the [`Checkbox`]. - /// - /// [`Color`]: ../../../../graphics/struct.Color.html - /// [`Checkbox`]: struct.Checkbox.html - pub fn label_color(mut self, color: Color) -> Self { - self.label_color = color; - self - } -} - -impl Widget for Checkbox -where - Renderer: self::Renderer + text::Renderer, -{ - fn node(&self, renderer: &Renderer) -> Node { - Row::<(), Renderer>::new() - .spacing(15) - .align_items(Align::Center) - .push(Column::new().width(28).height(28)) - .push(Text::new(&self.label)) - .node(renderer) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { - let mouse_over = layout - .children() - .any(|child| child.bounds().contains(cursor_position)); - - if mouse_over { - messages.push((self.on_toggle)(!self.is_checked)); - } - } - _ => {} - } - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - let children: Vec<_> = layout.children().collect(); - - let text_bounds = children[1].bounds(); - - text::Renderer::draw( - renderer, - text_bounds, - &self.label, - 20.0, - self.label_color, - HorizontalAlignment::Left, - VerticalAlignment::Top, - ); - - self::Renderer::draw( - renderer, - cursor_position, - children[0].bounds(), - text_bounds, - self.is_checked, - ) - } - - fn hash(&self, state: &mut Hasher) { - self.label.hash(state); - } -} - -/// The renderer of a [`Checkbox`]. -/// -/// Your [`core::Renderer`] will need to implement this trait before being -/// able to use a [`Checkbox`] in your user interface. -/// -/// [`Checkbox`]: struct.Checkbox.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -pub trait Renderer { - /// Draws a [`Checkbox`]. - /// - /// It receives: - /// * the current cursor position - /// * the bounds of the [`Checkbox`] - /// * the bounds of the label of the [`Checkbox`] - /// * whether the [`Checkbox`] is checked or not - /// - /// [`Checkbox`]: struct.Checkbox.html - fn draw( - &mut self, - cursor_position: Point, - bounds: Rectangle, - label_bounds: Rectangle, - is_checked: bool, - ) -> MouseCursor; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: self::Renderer + text::Renderer, - Message: 'static, -{ - fn from(checkbox: Checkbox) -> Element<'a, Message, Renderer> { - Element::new(checkbox) - } -} diff --git a/src/ui/widget/column.rs b/src/ui/widget/column.rs deleted file mode 100644 index 26ad415..0000000 --- a/src/ui/widget/column.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::hash::Hash; - -use crate::graphics::Point; -use crate::ui::core::{ - Align, Element, Event, Hasher, Justify, Layout, MouseCursor, Node, Style, - Widget, -}; - -/// A container that places its contents vertically. -/// -/// A [`Column`] will try to fill the horizontal space of its container. -/// -/// [`Column`]: struct.Column.html -pub struct Column<'a, Message, Renderer> { - style: Style, - spacing: u16, - children: Vec>, -} - -impl<'a, Message, Renderer> std::fmt::Debug for Column<'a, Message, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Column") - .field("style", &self.style) - .field("spacing", &self.spacing) - .field("children", &self.children) - .finish() - } -} - -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { - /// Creates an empty [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn new() -> Self { - let mut style = Style::default().fill_width(); - style.0.flex_direction = stretch::style::FlexDirection::Column; - - Column { - style, - spacing: 0, - children: Vec::new(), - } - } - - /// Sets the vertical spacing _between_ elements in pixels. - /// - /// Custom margins per element do not exist in Coffee. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, px: u16) -> Self { - self.spacing = px; - self - } - - /// Sets the padding of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn padding(mut self, px: u32) -> Self { - self.style = self.style.padding(px); - self - } - - /// Sets the width of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn width(mut self, width: u32) -> Self { - self.style = self.style.width(width); - self - } - - /// Sets the height of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn height(mut self, height: u32) -> Self { - self.style = self.style.height(height); - self - } - - /// Sets the maximum width of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.style = self.style.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.style = self.style.max_height(max_height); - self - } - - /// Sets the alignment of the [`Column`] itself. - /// - /// This is useful if you want to override the default alignment given by - /// the parent container. - /// - /// [`Column`]: struct.Column.html - pub fn align_self(mut self, align: Align) -> Self { - self.style = self.style.align_self(align); - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - /// - /// [`Column`]: struct.Column.html - pub fn align_items(mut self, align: Align) -> Self { - self.style = self.style.align_items(align); - self - } - - /// Sets the vertical distribution strategy for the contents of the - /// [`Column`] . - /// - /// [`Column`]: struct.Column.html - pub fn justify_content(mut self, justify: Justify) -> Self { - self.style = self.style.justify_content(justify); - self - } - - /// Adds an [`Element`] to the [`Column`]. - /// - /// [`Element`]: ../core/struct.Element.html - /// [`Column`]: struct.Column.html - pub fn push(mut self, child: E) -> Column<'a, Message, Renderer> - where - E: Into>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Widget - for Column<'a, Message, Renderer> -{ - fn node(&self, renderer: &Renderer) -> Node { - let mut children: Vec = self - .children - .iter() - .map(|child| { - let mut node = child.widget.node(renderer); - - let mut style = node.0.style(); - style.margin.bottom = - stretch::style::Dimension::Points(self.spacing as f32); - - node.0.set_style(style); - node - }) - .collect(); - - if let Some(node) = children.last_mut() { - let mut style = node.0.style(); - style.margin.bottom = stretch::style::Dimension::Undefined; - - node.0.set_style(style); - } - - Node::with_children(self.style, children) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { - child - .widget - .on_event(event, layout, cursor_position, messages) - }, - ); - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - let mut cursor = MouseCursor::OutOfBounds; - - self.children.iter().zip(layout.children()).for_each( - |(child, layout)| { - let new_cursor = - child.widget.draw(renderer, layout, cursor_position); - - if new_cursor != MouseCursor::OutOfBounds { - cursor = new_cursor; - } - }, - ); - - cursor - } - - fn hash(&self, state: &mut Hasher) { - self.style.hash(state); - self.spacing.hash(state); - - for child in &self.children { - child.widget.hash(state); - } - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: 'a, - Message: 'static, -{ - fn from( - column: Column<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(column) - } -} diff --git a/src/ui/widget/image.rs b/src/ui/widget/image.rs index 07db3cc..f0decf1 100644 --- a/src/ui/widget/image.rs +++ b/src/ui/widget/image.rs @@ -1,16 +1,14 @@ //! Displays image to your users. -use crate::graphics::{ - self, Rectangle, Point, -}; -use crate::ui::core:: { - Style, Node, Element, MouseCursor, Layout, Hasher, Widget, +use crate::graphics::{self, Rectangle}; +use crate::ui::core::{ + Element, Hasher, Layout, MouseCursor, Node, Style, Widget, }; use std::hash::Hash; /// A widget that displays an image. -/// +/// /// It implements [`Widget`] when the associated [`core::Renderer`] implements /// the [`image::Renderer`] trait. /// @@ -51,7 +49,7 @@ impl Image { } /// Sets the portion of the [`Image`] that we want to draw. - /// + /// /// [`Image`]: struct.Image.html pub fn clip(mut self, source: Rectangle) -> Self { self.source = source; @@ -61,7 +59,7 @@ impl Image { /// Sets the width of the [`Image`] boundaries in pixels. /// /// [`Image`]: struct.Image.html - pub fn width(mut self, width: u32) -> Self { + pub fn width(mut self, width: u16) -> Self { self.style = self.style.width(width); self } @@ -69,7 +67,7 @@ impl Image { /// Sets the height of the [`Image`] boundaries in pixels. /// /// [`Image`]: struct.Image.html - pub fn height(mut self, height: u32) -> Self { + pub fn height(mut self, height: u16) -> Self { self.style = self.style.height(height); self } @@ -77,7 +75,7 @@ impl Image { impl Widget for Image where - Renderer: self::Renderer + Renderer: self::Renderer, { fn node(&self, _renderer: &Renderer) -> Node { Node::new(self.style) @@ -87,18 +85,14 @@ where &self, renderer: &mut Renderer, layout: Layout<'_>, - _cursor_position: Point, + _cursor_position: iced::Point, ) -> MouseCursor { - renderer.draw( - layout.bounds(), - self.image.clone(), - self.source, - ); + renderer.draw(layout.bounds().into(), self.image.clone(), self.source); MouseCursor::OutOfBounds } - fn hash(&self, state: &mut Hasher) { + fn hash_layout(&self, state: &mut Hasher) { self.style.hash(state); } } @@ -117,7 +111,7 @@ pub trait Renderer { /// * the bounds of the [`Image`] /// * the handle of the loaded [`Image`] /// * the portion of the image that we wants to draw - /// + /// /// [`Image`]: struct.Image.html fn draw( &mut self, @@ -127,11 +121,11 @@ pub trait Renderer { ); } -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> Into> for Image where Renderer: self::Renderer, { - fn from(image: Image) -> Element<'a, Message, Renderer> { - Element::new(image) + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) } } diff --git a/src/ui/widget/progress_bar.rs b/src/ui/widget/progress_bar.rs index fdf32c7..67adbfb 100644 --- a/src/ui/widget/progress_bar.rs +++ b/src/ui/widget/progress_bar.rs @@ -1,16 +1,14 @@ //! Displays action progress to your users. -use crate::graphics::{ - Point, Rectangle, -}; +use crate::graphics::Rectangle; use crate::ui::core::{ - Style, Node, Element, MouseCursor, Layout, Hasher, Widget, + Element, Hasher, Layout, MouseCursor, Node, Style, Widget, }; use std::hash::Hash; /// A widget that displays a progress of an action. -/// +/// /// It implements [`Widget`] when the associated [`core::Renderer`] implements /// the [`button::Renderer`] trait. /// @@ -46,7 +44,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 } @@ -62,7 +60,7 @@ impl ProgressBar { impl Widget for ProgressBar where - Renderer: self::Renderer + Renderer: self::Renderer, { fn node(&self, _renderer: &Renderer) -> Node { Node::new(self.style.height(50)) @@ -72,17 +70,14 @@ where &self, renderer: &mut Renderer, layout: Layout<'_>, - _cursor_position: Point, + _cursor_position: iced::Point, ) -> MouseCursor { - renderer.draw( - layout.bounds(), - self.progress, - ); + renderer.draw(layout.bounds().into(), self.progress); MouseCursor::OutOfBounds } - fn hash(&self, state: &mut Hasher) { + fn hash_layout(&self, state: &mut Hasher) { self.style.hash(state); } } @@ -100,20 +95,16 @@ pub trait Renderer { /// It receives: /// * the bounds of the [`ProgressBar`] /// * the progress of the [`ProgressBar`] - /// + /// /// [`ProgressBar`]: struct.ProgressBar.html - fn draw( - &mut self, - bounds: Rectangle, - progress: f32, - ); + fn draw(&mut self, bounds: Rectangle, progress: f32); } -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> Into> for ProgressBar where Renderer: self::Renderer, { - fn from(progress_bar: ProgressBar) -> Element<'a, Message, Renderer> { - Element::new(progress_bar) + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) } } diff --git a/src/ui/widget/radio.rs b/src/ui/widget/radio.rs deleted file mode 100644 index 74c4449..0000000 --- a/src/ui/widget/radio.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Create choices using radio buttons. -use crate::graphics::{ - Color, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; -use crate::input::{mouse, ButtonState}; -use crate::ui::core::{ - Align, Element, Event, Hasher, Layout, MouseCursor, Node, Widget, -}; -use crate::ui::widget::{text, Column, Row, Text}; - -use std::hash::Hash; - -/// A circular button representing a choice. -/// -/// It implements [`Widget`] when the [`core::Renderer`] implements the -/// [`radio::Renderer`] trait. -/// -/// [`Widget`]: ../../core/trait.Widget.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -/// [`radio::Renderer`]: trait.Renderer.html -/// -/// # Example -/// ``` -/// use coffee::graphics::Color; -/// use coffee::ui::{Column, Radio}; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Column::new() -/// .spacing(20) -/// .push( -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected) -/// .label_color(Color::BLACK), -/// ) -/// .push( -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected) -/// .label_color(Color::BLACK), -/// ); -/// ``` -/// -/// ![Checkbox drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) -pub struct Radio { - is_selected: bool, - on_click: Message, - label: String, - label_color: Color, -} - -impl std::fmt::Debug for Radio -where - Message: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Radio") - .field("is_selected", &self.is_selected) - .field("on_click", &self.on_click) - .field("label", &self.label) - .field("label_color", &self.label_color) - .finish() - } -} - -impl Radio { - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - /// - /// [`Radio`]: struct.Radio.html - pub fn new(value: V, label: &str, selected: Option, f: F) -> Self - where - V: Eq + Copy, - F: 'static + Fn(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: String::from(label), - label_color: Color::WHITE, - } - } - - /// Sets the [`Color`] of the label of the [`Radio`]. - /// - /// [`Color`]: ../../../../graphics/struct.Color.html - /// [`Radio`]: struct.Radio.html - pub fn label_color(mut self, color: Color) -> Self { - self.label_color = color; - self - } -} - -impl Widget for Radio -where - Renderer: self::Renderer + text::Renderer, - Message: Copy + std::fmt::Debug, -{ - fn node(&self, renderer: &Renderer) -> Node { - Row::<(), Renderer>::new() - .spacing(15) - .align_items(Align::Center) - .push(Column::new().width(28).height(28)) - .push(Text::new(&self.label)) - .node(renderer) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { - if layout.bounds().contains(cursor_position) { - messages.push(self.on_click); - } - } - _ => {} - } - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - let children: Vec<_> = layout.children().collect(); - - let mut text_bounds = children[1].bounds(); - text_bounds.y -= 2.0; - - text::Renderer::draw( - renderer, - text_bounds, - &self.label, - 20.0, - self.label_color, - HorizontalAlignment::Left, - VerticalAlignment::Top, - ); - - self::Renderer::draw( - renderer, - cursor_position, - children[0].bounds(), - layout.bounds(), - self.is_selected, - ) - } - - fn hash(&self, state: &mut Hasher) { - self.label.hash(state); - } -} - -/// The renderer of a [`Radio`] button. -/// -/// Your [`core::Renderer`] will need to implement this trait before being -/// able to use a [`Radio`] button in your user interface. -/// -/// [`Radio`]: struct.Radio.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -pub trait Renderer { - /// Draws a [`Radio`] button. - /// - /// It receives: - /// * the current cursor position - /// * the bounds of the [`Radio`] - /// * the bounds of the label of the [`Radio`] - /// * whether the [`Radio`] is selected or not - /// - /// [`Radio`]: struct.Radio.html - fn draw( - &mut self, - cursor_position: Point, - bounds: Rectangle, - label_bounds: Rectangle, - is_selected: bool, - ) -> MouseCursor; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: self::Renderer + text::Renderer, - Message: 'static + Copy + std::fmt::Debug, -{ - fn from(checkbox: Radio) -> Element<'a, Message, Renderer> { - Element::new(checkbox) - } -} diff --git a/src/ui/widget/row.rs b/src/ui/widget/row.rs deleted file mode 100644 index 2fb61ef..0000000 --- a/src/ui/widget/row.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::hash::Hash; - -use crate::graphics::Point; -use crate::ui::core::{ - Align, Element, Event, Hasher, Justify, Layout, MouseCursor, Node, Style, - Widget, -}; - -/// A container that places its contents horizontally. -/// -/// A [`Row`] will try to fill the horizontal space of its container. -/// -/// [`Row`]: struct.Row.html -pub struct Row<'a, Message, Renderer> { - style: Style, - spacing: u16, - children: Vec>, -} - -impl<'a, Message, Renderer> std::fmt::Debug for Row<'a, Message, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Row") - .field("style", &self.style) - .field("spacing", &self.spacing) - .field("children", &self.children) - .finish() - } -} - -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { - /// Creates an empty [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn new() -> Self { - Row { - style: Style::default().fill_width(), - spacing: 0, - children: Vec::new(), - } - } - - /// Sets the horizontal spacing _between_ elements in pixels. - /// - /// Custom margins per element do not exist in Coffee. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, px: u16) -> Self { - self.spacing = px; - self - } - - /// Sets the padding of the [`Row`] in pixels. - /// - /// [`Row`]: struct.Row.html - pub fn padding(mut self, px: u32) -> Self { - self.style = self.style.padding(px); - self - } - - /// Sets the width of the [`Row`] in pixels. - /// - /// [`Row`]: struct.Row.html - pub fn width(mut self, width: u32) -> Self { - self.style = self.style.width(width); - self - } - - /// Sets the height of the [`Row`] in pixels. - /// - /// [`Row`]: struct.Row.html - pub fn height(mut self, height: u32) -> Self { - self.style = self.style.height(height); - self - } - - /// Sets the maximum width of the [`Row`] in pixels. - /// - /// [`Row`]: struct.Row.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.style = self.style.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Row`] in pixels. - /// - /// [`Row`]: struct.Row.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.style = self.style.max_height(max_height); - self - } - - /// Sets the alignment of the [`Row`] itself. - /// - /// This is useful if you want to override the default alignment given by - /// the parent container. - /// - /// [`Row`]: struct.Row.html - pub fn align_self(mut self, align: Align) -> Self { - self.style = self.style.align_self(align); - self - } - - /// Sets the vertical alignment of the contents of the [`Row`] . - /// - /// [`Row`]: struct.Row.html - pub fn align_items(mut self, align: Align) -> Self { - self.style = self.style.align_items(align); - self - } - - /// Sets the horizontal distribution strategy for the contents of the - /// [`Row`] . - /// - /// [`Row`]: struct.Row.html - pub fn justify_content(mut self, justify: Justify) -> Self { - self.style = self.style.justify_content(justify); - self - } - - /// Adds an [`Element`] to the [`Row`]. - /// - /// [`Element`]: ../core/struct.Element.html - /// [`Row`]: struct.Row.html - pub fn push(mut self, child: E) -> Row<'a, Message, Renderer> - where - E: Into>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Widget - for Row<'a, Message, Renderer> -{ - fn node(&self, renderer: &Renderer) -> Node { - let mut children: Vec = self - .children - .iter() - .map(|child| { - let mut node = child.widget.node(renderer); - - let mut style = node.0.style(); - style.margin.end = - stretch::style::Dimension::Points(self.spacing as f32); - - node.0.set_style(style); - node - }) - .collect(); - - if let Some(node) = children.last_mut() { - let mut style = node.0.style(); - style.margin.end = stretch::style::Dimension::Undefined; - - node.0.set_style(style); - } - - Node::with_children(self.style, children) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { - child - .widget - .on_event(event, layout, cursor_position, messages) - }, - ); - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - let mut cursor = MouseCursor::OutOfBounds; - - self.children.iter().zip(layout.children()).for_each( - |(child, layout)| { - let new_cursor = - child.widget.draw(renderer, layout, cursor_position); - - if new_cursor != MouseCursor::OutOfBounds { - cursor = new_cursor; - } - }, - ); - - cursor - } - - fn hash(&self, state: &mut Hasher) { - self.style.hash(state); - self.spacing.hash(state); - - for child in &self.children { - child.widget.hash(state); - } - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: 'a, - Message: 'static, -{ - fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { - Element::new(row) - } -} diff --git a/src/ui/widget/slider.rs b/src/ui/widget/slider.rs deleted file mode 100644 index 64d8893..0000000 --- a/src/ui/widget/slider.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -//! -//! [`Slider`]: struct.Slider.html -//! [`State`]: struct.State.html -use std::hash::Hash; -use std::ops::RangeInclusive; - -use crate::graphics::{Point, Rectangle}; -use crate::input::{mouse, ButtonState}; -use crate::ui::core::{ - Element, Event, Hasher, Layout, MouseCursor, Node, Style, Widget, -}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// It implements [`Widget`] when the associated [`core::Renderer`] implements -/// the [`slider::Renderer`] trait. -/// -/// [`Slider`]: struct.Slider.html -/// [`Widget`]: ../../core/trait.Widget.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -/// [`slider::Renderer`]: trait.Renderer.html -/// -/// # Example -/// ``` -/// use coffee::ui::{slider, Slider}; -/// -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let state = &mut slider::State::new(); -/// let value = 50.0; -/// -/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -/// ![Slider drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) -pub struct Slider<'a, Message> { - state: &'a mut State, - range: RangeInclusive, - value: f32, - on_change: Box Message>, - style: Style, -} - -impl<'a, Message> std::fmt::Debug for Slider<'a, Message> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Slider") - .field("state", &self.state) - .field("range", &self.range) - .field("value", &self.value) - .field("style", &self.style) - .finish() - } -} - -impl<'a, Message> Slider<'a, Message> { - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * the local [`State`] of the [`Slider`] - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - /// - /// [`Slider`]: struct.Slider.html - /// [`State`]: struct.State.html - pub fn new( - state: &'a mut State, - range: RangeInclusive, - value: f32, - on_change: F, - ) -> Self - where - F: 'static + Fn(f32) -> Message, - { - Slider { - state, - value: value.max(*range.start()).min(*range.end()), - range, - on_change: Box::new(on_change), - style: Style::default().min_width(100).fill_width(), - } - } - - /// Sets the width of the [`Slider`] in pixels. - /// - /// [`Slider`]: struct.Slider.html - pub fn width(mut self, width: u32) -> Self { - self.style = self.style.width(width); - self - } -} - -impl<'a, Message, Renderer> Widget for Slider<'a, Message> -where - Renderer: self::Renderer, -{ - fn node(&self, _renderer: &Renderer) -> Node { - Node::new(self.style.height(25)) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - ) { - let mut change = || { - let bounds = layout.bounds(); - - if cursor_position.x <= bounds.x { - messages.push((self.on_change)(*self.range.start())); - } else if cursor_position.x >= bounds.x + bounds.width { - messages.push((self.on_change)(*self.range.end())); - } else { - let percent = (cursor_position.x - bounds.x) / bounds.width; - let value = (self.range.end() - self.range.start()) * percent - + self.range.start(); - - messages.push((self.on_change)(value)); - } - }; - - match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { - if layout.bounds().contains(cursor_position) { - change(); - self.state.is_dragging = true; - } - } - ButtonState::Released => { - self.state.is_dragging = false; - } - }, - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if self.state.is_dragging { - change(); - } - } - _ => {} - } - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - renderer.draw( - cursor_position, - layout.bounds(), - self.state, - self.range.clone(), - self.value, - ) - } - - fn hash(&self, state: &mut Hasher) { - self.style.hash(state); - } -} - -/// The local state of a [`Slider`]. -/// -/// [`Slider`]: struct.Slider.html -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_dragging: bool, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() - } - - /// Returns whether the associated [`Slider`] is currently being dragged or - /// not. - /// - /// [`Slider`]: struct.Slider.html - pub fn is_dragging(&self) -> bool { - self.is_dragging - } -} - -/// The renderer of a [`Slider`]. -/// -/// Your [`core::Renderer`] will need to implement this trait before being -/// able to use a [`Slider`] in your user interface. -/// -/// [`Slider`]: struct.Slider.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -pub trait Renderer { - /// Draws a [`Slider`]. - /// - /// It receives: - /// * the current cursor position - /// * the bounds of the [`Slider`] - /// * the local state of the [`Slider`] - /// * the range of values of the [`Slider`] - /// * the current value of the [`Slider`] - /// - /// [`Slider`]: struct.Slider.html - /// [`State`]: struct.State.html - /// [`Class`]: enum.Class.html - fn draw( - &mut self, - cursor_position: Point, - bounds: Rectangle, - state: &State, - range: RangeInclusive, - value: f32, - ) -> MouseCursor; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: self::Renderer, - Message: 'static, -{ - fn from(slider: Slider<'a, Message>) -> Element<'a, Message, Renderer> { - Element::new(slider) - } -} diff --git a/src/ui/widget/text.rs b/src/ui/widget/text.rs index c93f21c..4bffa4d 100644 --- a/src/ui/widget/text.rs +++ b/src/ui/widget/text.rs @@ -1,7 +1,5 @@ //! Write some text for your users to read. -use crate::graphics::{ - Color, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; +use crate::graphics::{Color, HorizontalAlignment, VerticalAlignment}; use crate::ui::core::{ Element, Hasher, Layout, MouseCursor, Node, Style, Widget, }; @@ -32,11 +30,11 @@ use std::hash::Hash; #[derive(Debug, Clone)] pub struct Text { content: String, - size: u16, - color: Color, + size: Option, + color: Option, style: Style, - horizontal_alignment: HorizontalAlignment, - vertical_alignment: VerticalAlignment, + horizontal_alignment: iced::text::HorizontalAlignment, + vertical_alignment: iced::text::VerticalAlignment, } impl Text { @@ -46,11 +44,11 @@ impl Text { pub fn new(label: &str) -> Self { Text { content: String::from(label), - size: 20, - color: Color::WHITE, + size: None, + color: None, style: Style::default().fill_width(), - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Top, + horizontal_alignment: iced::text::HorizontalAlignment::Left, + vertical_alignment: iced::text::VerticalAlignment::Top, } } @@ -58,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 } @@ -67,14 +65,14 @@ impl Text { /// [`Text`]: struct.Text.html /// [`Color`]: ../../../graphics/struct.Color.html pub fn color(mut self, color: Color) -> Self { - self.color = color; + self.color = Some(color); self } /// 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 } @@ -82,7 +80,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 } @@ -95,7 +93,7 @@ impl Text { mut self, alignment: HorizontalAlignment, ) -> Self { - self.horizontal_alignment = alignment; + self.horizontal_alignment = alignment.into(); self } @@ -104,29 +102,29 @@ impl Text { /// [`Text`]: struct.Text.html /// [`VerticalAlignment`]: ../../../graphics/enum.VerticalAlignment.html pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self { - self.vertical_alignment = alignment; + self.vertical_alignment = alignment.into(); self } } impl Widget for Text where - Renderer: self::Renderer, + Renderer: iced::text::Renderer, { fn node(&self, renderer: &Renderer) -> Node { - renderer.node(self.style, &self.content, self.size as f32) + renderer.node(self.style, &self.content, self.size) } fn draw( &self, renderer: &mut Renderer, layout: Layout<'_>, - _cursor_position: Point, + _cursor_position: iced::Point, ) -> MouseCursor { renderer.draw( layout.bounds(), &self.content, - self.size as f32, + self.size, self.color, self.horizontal_alignment, self.vertical_alignment, @@ -135,7 +133,7 @@ where MouseCursor::OutOfBounds } - fn hash(&self, state: &mut Hasher) { + fn hash_layout(&self, state: &mut Hasher) { self.style.hash(state); self.content.hash(state); @@ -143,55 +141,11 @@ where } } -/// The renderer of a [`Text`] fragment. -/// -/// Your [`core::Renderer`] will need to implement this trait before being -/// able to use a [`Text`] in your user interface. -/// -/// [`Text`]: struct.Text.html -/// [`core::Renderer`]: ../../core/trait.Renderer.html -pub trait Renderer { - /// Creates a [`Node`] with the given [`Style`] for the provided [`Text`] - /// contents and size. - /// - /// You should probably use [`Node::with_measure`] to allow [`Text`] to - /// adapt to the dimensions of its container. - /// - /// [`Node`]: ../../core/struct.Node.html - /// [`Style`]: ../../core/struct.Style.html - /// [`Text`]: struct.Text.html - /// [`Node::with_measure`]: ../../core/struct.Node.html#method.with_measure - fn node(&self, style: Style, content: &str, size: f32) -> Node; - - /// Draws a [`Text`] fragment. - /// - /// It receives: - /// * the bounds of the [`Text`] - /// * the contents of the [`Text`] - /// * the size of the [`Text`] - /// * the color of the [`Text`] - /// * the [`HorizontalAlignment`] of the [`Text`] - /// * the [`VerticalAlignment`] of the [`Text`] - /// - /// [`Text`]: struct.Text.html - /// [`HorizontalAlignment`]: ../../../graphics/enum.HorizontalAlignment.html - /// [`VerticalAlignment`]: ../../../graphics/enum.VerticalAlignment.html - fn draw( - &mut self, - bounds: Rectangle, - content: &str, - size: f32, - color: Color, - horizontal_alignment: HorizontalAlignment, - vertical_alignment: VerticalAlignment, - ); -} - -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> Into> for Text where - Renderer: self::Renderer, + Renderer: iced::text::Renderer, { - fn from(text: Text) -> Element<'a, Message, Renderer> { - Element::new(text) + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) } }