diff options
| -rw-r--r-- | src/settings.rs | 2 | ||||
| -rw-r--r-- | src/window.rs | 3 | ||||
| -rw-r--r-- | src/window/icon.rs | 132 | ||||
| -rw-r--r-- | src/window/settings.rs | 9 | ||||
| -rw-r--r-- | web/src/widget/checkbox.rs | 28 | ||||
| -rw-r--r-- | web/src/widget/image.rs | 16 | ||||
| -rw-r--r-- | web/src/widget/radio.rs | 45 | ||||
| -rw-r--r-- | winit/src/settings.rs | 9 | 
8 files changed, 230 insertions, 14 deletions
diff --git a/src/settings.rs b/src/settings.rs index d7ff4cab..40b1b1ea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@  use crate::window;  /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone)]  pub struct Settings<Flags> {      /// The window settings.      /// diff --git a/src/window.rs b/src/window.rs index 54ea2a02..a2883b62 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,5 +2,8 @@  mod mode;  mod settings; +pub mod icon; + +pub use icon::Icon;  pub use mode::Mode;  pub use settings::Settings; diff --git a/src/window/icon.rs b/src/window/icon.rs new file mode 100644 index 00000000..15e0312d --- /dev/null +++ b/src/window/icon.rs @@ -0,0 +1,132 @@ +//! Attach an icon to the window of your application. +use std::fmt; +use std::io; + +/// The icon of a window. +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, Clone)] +pub struct Icon(iced_winit::winit::window::Icon); + +/// The icon of a window. +#[cfg(target_arch = "wasm32")] +#[derive(Debug, Clone)] +pub struct Icon; + +impl Icon { +    /// Creates an icon from 32bpp RGBA data. +    #[cfg(not(target_arch = "wasm32"))] +    pub fn from_rgba( +        rgba: Vec<u8>, +        width: u32, +        height: u32, +    ) -> Result<Self, Error> { +        let raw = +            iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?; + +        Ok(Icon(raw)) +    } + +    /// Creates an icon from 32bpp RGBA data. +    #[cfg(target_arch = "wasm32")] +    pub fn from_rgba( +        _rgba: Vec<u8>, +        _width: u32, +        _height: u32, +    ) -> Result<Self, Error> { +        Ok(Icon) +    } +} + +/// An error produced when using `Icon::from_rgba` with invalid arguments. +#[derive(Debug)] +pub enum Error { +    /// The provided RGBA data isn't divisble by 4. +    /// +    /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels. +    InvalidData { +        /// The length of the provided RGBA data. +        byte_count: usize, +    }, + +    /// The number of RGBA pixels does not match the provided dimensions. +    DimensionsMismatch { +        /// The provided width. +        width: u32, +        /// The provided height. +        height: u32, +        /// The amount of pixels of the provided RGBA data. +        pixel_count: usize, +    }, + +    /// The underlying OS failed to create the icon. +    OsError(io::Error), +} + +#[cfg(not(target_arch = "wasm32"))] +impl From<iced_winit::winit::window::BadIcon> for Error { +    fn from(error: iced_winit::winit::window::BadIcon) -> Self { +        use iced_winit::winit::window::BadIcon; + +        match error { +            BadIcon::ByteCountNotDivisibleBy4 { byte_count } => { +                Error::InvalidData { byte_count } +            } +            BadIcon::DimensionsVsPixelCount { +                width, +                height, +                pixel_count, +                .. +            } => Error::DimensionsMismatch { +                width, +                height, +                pixel_count, +            }, +            BadIcon::OsError(os_error) => Error::OsError(os_error), +        } +    } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From<Icon> for iced_winit::winit::window::Icon { +    fn from(icon: Icon) -> Self { +        icon.0 +    } +} + +impl fmt::Display for Error { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        match self { +            Error::InvalidData { byte_count } => { +                write!(f, +                "The provided RGBA data (with length {:?}) isn't divisble by \ +                4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ +                pixels.", +                byte_count, +            ) +            } +            Error::DimensionsMismatch { +                width, +                height, +                pixel_count, +            } => { +                write!(f, +                "The number of RGBA pixels ({:?}) does not match the provided \ +                dimensions ({:?}x{:?}).", +                width, height, pixel_count, +            ) +            } +            Error::OsError(e) => write!( +                f, +                "The underlying OS failed to create the window \ +                icon: {:?}", +                e +            ), +        } +    } +} + +impl std::error::Error for Error { +    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +        Some(self) +    } +} diff --git a/src/window/settings.rs b/src/window/settings.rs index eb997899..2046f2d9 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,5 +1,7 @@ +use crate::window::Icon; +  /// The window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone)]  pub struct Settings {      /// The initial size of the window.      pub size: (u32, u32), @@ -15,6 +17,9 @@ pub struct Settings {      /// Whether the window should have a border, a title bar, etc. or not.      pub decorations: bool, + +    /// The icon of the window. +    pub icon: Option<Icon>,  }  impl Default for Settings { @@ -25,6 +30,7 @@ impl Default for Settings {              max_size: None,              resizable: true,              decorations: true, +            icon: None,          }      }  } @@ -38,6 +44,7 @@ impl From<Settings> for iced_winit::settings::Window {              max_size: settings.max_size,              resizable: settings.resizable,              decorations: settings.decorations, +            icon: settings.icon.map(Icon::into),              platform_specific: Default::default(),          }      } diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 5ebc26c8..4d0c7c17 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -28,6 +28,7 @@ pub struct Checkbox<Message> {      is_checked: bool,      on_toggle: Rc<dyn Fn(bool) -> Message>,      label: String, +    id: Option<String>,      width: Length,      style: Box<dyn StyleSheet>,  } @@ -51,6 +52,7 @@ impl<Message> Checkbox<Message> {              is_checked,              on_toggle: Rc::new(f),              label: label.into(), +            id: None,              width: Length::Shrink,              style: Default::default(),          } @@ -71,6 +73,14 @@ impl<Message> Checkbox<Message> {          self.style = style.into();          self      } + +    /// Sets the id of the [`Checkbox`]. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    pub fn id(mut self, id: impl Into<String>) -> Self { +        self.id = Some(id.into()); +        self +    }  }  impl<Message> Widget<Message> for Checkbox<Message> @@ -85,7 +95,8 @@ where      ) -> dodrio::Node<'b> {          use dodrio::builder::*; -        let checkbox_label = bumpalo::format!(in bump, "{}", self.label); +        let checkbox_label = +            bumpalo::format!(in bump, "{}", self.label).into_bump_str();          let event_bus = bus.clone();          let on_toggle = self.on_toggle.clone(); @@ -95,7 +106,15 @@ where          let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); -        label(bump) +        let (label, input) = if let Some(id) = &self.id { +            let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + +            (label(bump).attr("for", id), input(bump).attr("id", id)) +        } else { +            (label(bump), input(bump)) +        }; + +        label              .attr(                  "class",                  bumpalo::format!(in bump, "{} {}", row_class, spacing_class) @@ -108,7 +127,7 @@ where              )              .children(vec![                  // TODO: Checkbox styling -                input(bump) +                 input                      .attr("type", "checkbox")                      .bool_attr("checked", self.is_checked)                      .on("click", move |_root, vdom, _event| { @@ -118,8 +137,7 @@ where                          vdom.schedule_render();                      })                      .finish(), -                span(bump).children(vec![ -                text(checkbox_label.into_bump_str())]).finish(), +                text(checkbox_label),              ])              .finish()      } diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index 029ab352..a20bebea 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -22,6 +22,9 @@ pub struct Image {      /// The image path      pub handle: Handle, +    /// The alt text of the image +    pub alt: String, +      /// The width of the image      pub width: Length, @@ -36,6 +39,7 @@ impl Image {      pub fn new<T: Into<Handle>>(handle: T) -> Self {          Image {              handle: handle.into(), +            alt: Default::default(),              width: Length::Shrink,              height: Length::Shrink,          } @@ -56,6 +60,14 @@ impl Image {          self.height = height;          self      } + +    /// Sets the alt text of the [`Image`]. +    /// +    /// [`Image`]: struct.Image.html +    pub fn alt(mut self, alt: impl Into<String>) -> Self { +        self.alt = alt.into(); +        self +    }  }  impl<Message> Widget<Message> for Image { @@ -70,8 +82,10 @@ impl<Message> Widget<Message> for Image {          let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() {              Data::Path(path) => path.to_str().unwrap_or("")          }); +        let alt = bumpalo::format!(in bump, "{}", self.alt).into_bump_str(); -        let mut image = img(bump).attr("src", src.into_bump_str()); +        let mut image = +            img(bump).attr("src", src.into_bump_str()).attr("alt", alt);          match self.width {              Length::Shrink => {} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 520b24cd..fae67cd8 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -35,6 +35,8 @@ pub struct Radio<Message> {      is_selected: bool,      on_click: Message,      label: String, +    id: Option<String>, +    name: Option<String>,      style: Box<dyn StyleSheet>,  } @@ -63,6 +65,8 @@ impl<Message> Radio<Message> {              is_selected: Some(value) == selected,              on_click: f(value),              label: label.into(), +            id: None, +            name: None,              style: Default::default(),          }      } @@ -74,6 +78,22 @@ impl<Message> Radio<Message> {          self.style = style.into();          self      } + +    /// Sets the name attribute of the [`Radio`] button. +    /// +    /// [`Radio`]: struct.Radio.html +    pub fn name(mut self, name: impl Into<String>) -> Self { +        self.name = Some(name.into()); +        self +    } + +    /// Sets the id of the [`Radio`] button. +    /// +    /// [`Radio`]: struct.Radio.html +    pub fn id(mut self, id: impl Into<String>) -> Self { +        self.id = Some(id.into()); +        self +    }  }  impl<Message> Widget<Message> for Radio<Message> @@ -88,16 +108,33 @@ where      ) -> dodrio::Node<'b> {          use dodrio::builder::*; -        let radio_label = bumpalo::format!(in bump, "{}", self.label); +        let radio_label = +            bumpalo::format!(in bump, "{}", self.label).into_bump_str();          let event_bus = bus.clone();          let on_click = self.on_click.clone(); +        let (label, input) = if let Some(id) = &self.id { +            let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + +            (label(bump).attr("for", id), input(bump).attr("id", id)) +        } else { +            (label(bump), input(bump)) +        }; + +        let input = if let Some(name) = &self.name { +            let name = bumpalo::format!(in bump, "{}", name).into_bump_str(); + +            dodrio::builder::input(bump).attr("name", name) +        } else { +            input +        }; +          // TODO: Complete styling -        label(bump) +        label              .attr("style", "display: block; font-size: 20px")              .children(vec![ -                input(bump) +                input                      .attr("type", "radio")                      .attr("style", "margin-right: 10px")                      .bool_attr("checked", self.is_selected) @@ -105,7 +142,7 @@ where                          event_bus.publish(on_click.clone());                      })                      .finish(), -                text(radio_label.into_bump_str()), +                text(radio_label),              ])              .finish()      } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 6799f23f..4155bf7d 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -14,7 +14,7 @@ use winit::monitor::MonitorHandle;  use winit::window::WindowBuilder;  /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Default)]  pub struct Settings<Flags> {      /// The [`Window`] settings      /// @@ -28,7 +28,7 @@ pub struct Settings<Flags> {  }  /// The window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone)]  pub struct Window {      /// The size of the window.      pub size: (u32, u32), @@ -45,6 +45,9 @@ pub struct Window {      /// Whether the window should have a border, a title bar, etc.      pub decorations: bool, +    /// The window icon, which is also usually used in the taskbar +    pub icon: Option<winit::window::Icon>, +      /// Platform specific settings.      pub platform_specific: platform::PlatformSpecific,  } @@ -66,6 +69,7 @@ impl Window {              .with_inner_size(winit::dpi::LogicalSize { width, height })              .with_resizable(self.resizable)              .with_decorations(self.decorations) +            .with_window_icon(self.icon)              .with_fullscreen(conversion::fullscreen(primary_monitor, mode));          if let Some((width, height)) = self.min_size { @@ -99,6 +103,7 @@ impl Default for Window {              max_size: None,              resizable: true,              decorations: true, +            icon: None,              platform_specific: Default::default(),          }      }  | 
