summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-11-30 02:14:56 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-11-30 02:14:56 +0100
commitcdd34e1e4b741a97dc6da8813a00d792eda8172a (patch)
treead074a7c840d379aadcf4efb9fb0c25df30479a4
parent505588d5851fed8a3bb334edacfa6d96c545d562 (diff)
downloadiced-cdd34e1e4b741a97dc6da8813a00d792eda8172a.tar.gz
iced-cdd34e1e4b741a97dc6da8813a00d792eda8172a.tar.bz2
iced-cdd34e1e4b741a97dc6da8813a00d792eda8172a.zip
Implement `image` viewer example
Diffstat (limited to '')
-rw-r--r--Cargo.toml1
-rw-r--r--core/src/color.rs12
-rw-r--r--examples/image.rs202
-rw-r--r--native/src/widget/image.rs39
-rw-r--r--src/native.rs9
5 files changed, 245 insertions, 18 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 7039a5b3..71d3b3cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,6 +39,7 @@ env_logger = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
directories = "2.0"
+reqwest = "0.9"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen = "0.2.51"
diff --git a/core/src/color.rs b/core/src/color.rs
index ec48c185..7b3649ad 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -25,6 +25,18 @@ impl Color {
a: 1.0,
};
+ /// Creates a [`Color`] from its RGB8 components.
+ ///
+ /// [`Color`]: struct.Color.html
+ pub fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
+ Color {
+ r: f32::from(r) / 255.0,
+ g: f32::from(g) / 255.0,
+ b: f32::from(b) / 255.0,
+ a: 1.0,
+ }
+ }
+
/// Converts the [`Color`] into its linear values.
///
/// [`Color`]: struct.Color.html
diff --git a/examples/image.rs b/examples/image.rs
new file mode 100644
index 00000000..a64c0782
--- /dev/null
+++ b/examples/image.rs
@@ -0,0 +1,202 @@
+use iced::{
+ button, image, Align, Application, Background, Button, Color, Column,
+ Command, Container, Element, HorizontalAlignment, Image, Length, Row,
+ Settings, Text,
+};
+use serde::Deserialize;
+
+pub fn main() {
+ Example::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Example {
+ cats_button: button::State,
+ dogs_button: button::State,
+ image: Option<image::Handle>,
+ state: State,
+}
+
+enum State {
+ Idle,
+ Loading(Pet),
+ Error(LoadError),
+}
+
+impl Default for State {
+ fn default() -> State {
+ State::Idle
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ PetChosen(Pet),
+ ImageLoaded(Result<image::Handle, LoadError>),
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Pet {
+ Cat,
+ Dog,
+}
+
+impl Application for Example {
+ type Message = Message;
+
+ fn new() -> (Self, Command<Message>) {
+ (Self::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Image viewer - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::PetChosen(pet) => match self.state {
+ State::Loading(_) => Command::none(),
+ _ => {
+ self.state = State::Loading(pet);
+
+ Command::perform(get_pet_image(pet), Message::ImageLoaded)
+ }
+ },
+ Message::ImageLoaded(Ok(image)) => {
+ self.image = Some(image);
+ self.state = State::Idle;
+
+ Command::none()
+ }
+ Message::ImageLoaded(Err(error)) => {
+ self.state = State::Error(error);
+
+ Command::none()
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let Example {
+ cats_button,
+ dogs_button,
+ state,
+ image,
+ } = self;
+
+ let choose: Element<_> = match state {
+ State::Loading(pet) => Text::new(format!(
+ "Getting your {} ready...",
+ match pet {
+ Pet::Cat => "cat",
+ Pet::Dog => "dog",
+ }
+ ))
+ .width(Length::Shrink)
+ .color([0.4, 0.4, 0.4])
+ .into(),
+ _ => Row::new()
+ .width(Length::Shrink)
+ .spacing(20)
+ .push(
+ button(
+ cats_button,
+ "Cats",
+ Color::from_rgb8(0x89, 0x80, 0xF5),
+ )
+ .on_press(Message::PetChosen(Pet::Cat)),
+ )
+ .push(
+ button(
+ dogs_button,
+ "Dogs",
+ Color::from_rgb8(0x21, 0xD1, 0x9F),
+ )
+ .on_press(Message::PetChosen(Pet::Dog)),
+ )
+ .into(),
+ };
+
+ let content = Column::new()
+ .width(Length::Shrink)
+ .padding(20)
+ .spacing(20)
+ .align_items(Align::Center)
+ .push(
+ Text::new("What do you want to see?")
+ .width(Length::Shrink)
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .size(40),
+ )
+ .push(choose);
+
+ let content = if let Some(image) = image {
+ content.push(Image::new(image.clone()).height(Length::Fill))
+ } else {
+ content
+ };
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
+
+fn button<'a, Message>(
+ state: &'a mut button::State,
+ label: &str,
+ color: Color,
+) -> Button<'a, Message> {
+ Button::new(
+ state,
+ Text::new(label)
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .color(Color::WHITE)
+ .size(30),
+ )
+ .padding(10)
+ .min_width(100)
+ .border_radius(10)
+ .background(Background::Color(color))
+}
+
+#[derive(Debug, Deserialize)]
+pub struct SearchResult {
+ url: String,
+}
+
+#[derive(Debug, Clone)]
+enum LoadError {
+ RequestError,
+}
+
+async fn get_pet_image(pet: Pet) -> Result<image::Handle, LoadError> {
+ use std::io::Read;
+
+ let search = match pet {
+ Pet::Cat => "https://api.thecatapi.com/v1/images/search?limit=1&mime_types=jpg,png",
+ Pet::Dog => "https://api.thedogapi.com/v1/images/search?limit=1&mime_types=jpg,png",
+ };
+
+ let results: Vec<SearchResult> = reqwest::get(search)?.json()?;
+ let url = &results.first().unwrap().url;
+
+ let mut image = reqwest::get(url)?;
+ let mut bytes = Vec::new();
+
+ image
+ .read_to_end(&mut bytes)
+ .map_err(|_| LoadError::RequestError)?;
+
+ Ok(image::Handle::from_bytes(bytes))
+}
+
+impl From<reqwest::Error> for LoadError {
+ fn from(error: reqwest::Error) -> LoadError {
+ dbg!(&error);
+ LoadError::RequestError
+ }
+}
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index c82525e4..aaa3eca5 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -5,7 +5,7 @@ use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
path::PathBuf,
- rc::Rc,
+ sync::Arc,
};
/// A frame that displays an image while keeping aspect ratio.
@@ -76,20 +76,18 @@ where
let aspect_ratio = width as f32 / height as f32;
- // TODO: Deal with additional cases
- let (width, height) = match (self.width, self.height) {
- (Length::Units(width), _) => (
- self.width,
- Length::Units((width as f32 / aspect_ratio).round() as u16),
- ),
- (_, _) => {
- (Length::Units(width as u16), Length::Units(height as u16))
- }
- };
+ let mut size = limits
+ .width(self.width)
+ .height(self.height)
+ .resolve(Size::new(width as f32, height as f32));
- let mut size = limits.width(width).height(height).resolve(Size::ZERO);
+ let viewport_aspect_ratio = size.width / size.height;
- size.height = size.width / aspect_ratio;
+ if viewport_aspect_ratio > aspect_ratio {
+ size.width = width as f32 * size.height / height as f32;
+ } else {
+ size.height = height as f32 * size.width / width as f32;
+ }
layout::Node::new(size)
}
@@ -115,7 +113,7 @@ where
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
- data: Rc<Data>,
+ data: Arc<Data>,
}
impl Handle {
@@ -139,7 +137,7 @@ impl Handle {
Handle {
id: hasher.finish(),
- data: Rc::new(data),
+ data: Arc::new(data),
}
}
@@ -173,7 +171,7 @@ impl From<&str> for Handle {
/// The data of an [`Image`].
///
/// [`Image`]: struct.Image.html
-#[derive(Debug, Clone, Hash)]
+#[derive(Clone, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
@@ -182,6 +180,15 @@ pub enum Data {
Bytes(Vec<u8>),
}
+impl std::fmt::Debug for Data {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Bytes(_) => write!(f, "Bytes(...)"),
+ }
+ }
+}
+
/// The renderer of an [`Image`].
///
/// Your [renderer] will need to implement this trait before being able to use
diff --git a/src/native.rs b/src/native.rs
index 926b2d11..3537dd52 100644
--- a/src/native.rs
+++ b/src/native.rs
@@ -75,11 +75,16 @@ pub mod widget {
pub use iced_winit::slider::{Slider, State};
}
- pub use iced_winit::{Checkbox, Image, Radio, Text};
+ pub mod image {
+ //! Display images in your user interface.
+ pub use iced_winit::image::{Handle, Image};
+ }
+
+ pub use iced_winit::{Checkbox, Radio, Text};
#[doc(no_inline)]
pub use {
- button::Button, scrollable::Scrollable, slider::Slider,
+ button::Button, image::Image, scrollable::Scrollable, slider::Slider,
text_input::TextInput,
};