Implement image
viewer example
This commit is contained in:
parent
505588d585
commit
cdd34e1e4b
@ -39,6 +39,7 @@ env_logger = "0.7"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
directories = "2.0"
|
directories = "2.0"
|
||||||
|
reqwest = "0.9"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
wasm-bindgen = "0.2.51"
|
wasm-bindgen = "0.2.51"
|
||||||
|
@ -25,6 +25,18 @@ impl Color {
|
|||||||
a: 1.0,
|
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.
|
/// Converts the [`Color`] into its linear values.
|
||||||
///
|
///
|
||||||
/// [`Color`]: struct.Color.html
|
/// [`Color`]: struct.Color.html
|
||||||
|
202
examples/image.rs
Normal file
202
examples/image.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
|
|||||||
use std::{
|
use std::{
|
||||||
hash::{Hash, Hasher as _},
|
hash::{Hash, Hasher as _},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
rc::Rc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A frame that displays an image while keeping aspect ratio.
|
/// A frame that displays an image while keeping aspect ratio.
|
||||||
@ -76,20 +76,18 @@ where
|
|||||||
|
|
||||||
let aspect_ratio = width as f32 / height as f32;
|
let aspect_ratio = width as f32 / height as f32;
|
||||||
|
|
||||||
// TODO: Deal with additional cases
|
let mut size = limits
|
||||||
let (width, height) = match (self.width, self.height) {
|
.width(self.width)
|
||||||
(Length::Units(width), _) => (
|
.height(self.height)
|
||||||
self.width,
|
.resolve(Size::new(width as f32, height as f32));
|
||||||
Length::Units((width as f32 / aspect_ratio).round() as u16),
|
|
||||||
),
|
let viewport_aspect_ratio = size.width / size.height;
|
||||||
(_, _) => {
|
|
||||||
(Length::Units(width as u16), Length::Units(height as u16))
|
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;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut size = limits.width(width).height(height).resolve(Size::ZERO);
|
|
||||||
|
|
||||||
size.height = size.width / aspect_ratio;
|
|
||||||
|
|
||||||
layout::Node::new(size)
|
layout::Node::new(size)
|
||||||
}
|
}
|
||||||
@ -115,7 +113,7 @@ where
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
id: u64,
|
id: u64,
|
||||||
data: Rc<Data>,
|
data: Arc<Data>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle {
|
impl Handle {
|
||||||
@ -139,7 +137,7 @@ impl Handle {
|
|||||||
|
|
||||||
Handle {
|
Handle {
|
||||||
id: hasher.finish(),
|
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`].
|
/// The data of an [`Image`].
|
||||||
///
|
///
|
||||||
/// [`Image`]: struct.Image.html
|
/// [`Image`]: struct.Image.html
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub enum Data {
|
pub enum Data {
|
||||||
/// File data
|
/// File data
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
@ -182,6 +180,15 @@ pub enum Data {
|
|||||||
Bytes(Vec<u8>),
|
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`].
|
/// The renderer of an [`Image`].
|
||||||
///
|
///
|
||||||
/// Your [renderer] will need to implement this trait before being able to use
|
/// Your [renderer] will need to implement this trait before being able to use
|
||||||
|
@ -75,11 +75,16 @@ pub mod widget {
|
|||||||
pub use iced_winit::slider::{Slider, State};
|
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)]
|
#[doc(no_inline)]
|
||||||
pub use {
|
pub use {
|
||||||
button::Button, scrollable::Scrollable, slider::Slider,
|
button::Button, image::Image, scrollable::Scrollable, slider::Slider,
|
||||||
text_input::TextInput,
|
text_input::TextInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user