Merge pull request #180 from hecrj/feature/web-styling
Custom styling for `iced_web`
This commit is contained in:
commit
97c308076f
11
Cargo.toml
11
Cargo.toml
@ -12,12 +12,16 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||
categories = ["gui"]
|
||||
|
||||
[features]
|
||||
# Enables the Image widget
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_wgpu/image"]
|
||||
# Enables the Svg widget
|
||||
# Enables the `Svg` widget
|
||||
svg = ["iced_wgpu/svg"]
|
||||
# Enables a debug view in native platforms (press F12)
|
||||
debug = ["iced_winit/debug"]
|
||||
# Enables `tokio` as the `executor::Default` on native platforms
|
||||
tokio = ["iced_futures/tokio"]
|
||||
# Enables `async-std` as the `executor::Default` on native platforms
|
||||
async-std = ["iced_futures/async-std"]
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
@ -45,6 +49,9 @@ members = [
|
||||
"examples/tour",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
iced_futures = { version = "0.1.0-alpha", path = "futures" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
iced_winit = { version = "0.1.0-alpha", path = "winit" }
|
||||
iced_wgpu = { version = "0.1.0", path = "wgpu" }
|
||||
|
@ -6,9 +6,13 @@ edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["image"] }
|
||||
iced_futures = { path = "../../futures", features = ["async-std"] }
|
||||
surf = "1.0"
|
||||
rand = "0.7"
|
||||
iced = { path = "../..", features = ["image", "debug", "tokio"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rand = { version = "0.7", features = ["wasm-bindgen"] }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.10"
|
||||
git = "https://github.com/hecrj/reqwest.git"
|
||||
branch = "feature/wasm-deserialize-json"
|
||||
features = ["json"]
|
||||
|
@ -27,7 +27,7 @@ enum Message {
|
||||
}
|
||||
|
||||
impl Application for Pokedex {
|
||||
type Executor = iced_futures::executor::AsyncStd;
|
||||
type Executor = iced::executor::Default;
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> (Pokedex, Command<Message>) {
|
||||
@ -79,6 +79,7 @@ impl Application for Pokedex {
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let content = match self {
|
||||
Pokedex::Loading => Column::new()
|
||||
.width(Length::Shrink)
|
||||
.push(Text::new("Searching for Pokémon...").size(40)),
|
||||
Pokedex::Loaded { pokemon, search } => Column::new()
|
||||
.max_width(500)
|
||||
@ -166,19 +167,21 @@ impl Pokemon {
|
||||
}
|
||||
|
||||
let id = {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = rand::rngs::OsRng::default();
|
||||
|
||||
rng.gen_range(0, Pokemon::TOTAL)
|
||||
};
|
||||
|
||||
let url = format!("https://pokeapi.co/api/v2/pokemon-species/{}", id);
|
||||
let sprite = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id);
|
||||
let fetch_entry = async {
|
||||
let url =
|
||||
format!("https://pokeapi.co/api/v2/pokemon-species/{}", id);
|
||||
|
||||
let (entry, sprite): (Entry, _) = futures::future::try_join(
|
||||
surf::get(&url).recv_json(),
|
||||
surf::get(&sprite).recv_bytes(),
|
||||
)
|
||||
.await?;
|
||||
reqwest::get(&url).await?.json().await
|
||||
};
|
||||
|
||||
let (entry, image): (Entry, _) =
|
||||
futures::future::try_join(fetch_entry, Self::fetch_image(id))
|
||||
.await?;
|
||||
|
||||
let description = entry
|
||||
.flavor_text_entries
|
||||
@ -195,9 +198,23 @@ impl Pokemon {
|
||||
.chars()
|
||||
.map(|c| if c.is_control() { ' ' } else { c })
|
||||
.collect(),
|
||||
image: image::Handle::from_memory(sprite),
|
||||
image,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
||||
let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let bytes = reqwest::get(&url).await?.bytes().await?;
|
||||
|
||||
Ok(image::Handle::from_memory(bytes.as_ref().to_vec()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Ok(image::Handle::from_path(url))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -206,9 +223,9 @@ enum Error {
|
||||
LanguageError,
|
||||
}
|
||||
|
||||
impl From<surf::Exception> for Error {
|
||||
fn from(exception: surf::Exception) -> Error {
|
||||
dbg!(&exception);
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(error: reqwest::Error) -> Error {
|
||||
dbg!(&error);
|
||||
|
||||
Error::APIError
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ impl Sandbox for Styling {
|
||||
"Toggle me!",
|
||||
Message::CheckboxToggled,
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.style(self.theme);
|
||||
|
||||
let content = Column::new()
|
||||
|
@ -6,13 +6,18 @@ edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_futures = { path = "../../futures", features = ["async-std"] }
|
||||
async-std = "1.0"
|
||||
iced = { path = "../..", features = ["async-std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
async-std = "1.0"
|
||||
directories = "2.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features = ["Window", "Storage"] }
|
||||
wasm-timer = "0.2"
|
||||
|
||||
[package.metadata.deb]
|
||||
assets = [
|
||||
["target/release/todos", "usr/bin/iced-todos", "755"],
|
||||
|
@ -38,7 +38,7 @@ enum Message {
|
||||
}
|
||||
|
||||
impl Application for Todos {
|
||||
type Executor = iced_futures::executor::AsyncStd;
|
||||
type Executor = iced::executor::Default;
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> (Todos, Command<Message>) {
|
||||
@ -377,6 +377,7 @@ impl Controls {
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.width(Length::Shrink)
|
||||
.spacing(10)
|
||||
.push(filter_button(
|
||||
all_button,
|
||||
@ -493,6 +494,7 @@ enum SaveError {
|
||||
FormatError,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl SavedState {
|
||||
fn path() -> std::path::PathBuf {
|
||||
let mut path = if let Some(project_dirs) =
|
||||
@ -555,6 +557,41 @@ impl SavedState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl SavedState {
|
||||
fn storage() -> Option<web_sys::Storage> {
|
||||
let window = web_sys::window()?;
|
||||
|
||||
window.local_storage().ok()?
|
||||
}
|
||||
|
||||
async fn load() -> Result<SavedState, LoadError> {
|
||||
let storage = Self::storage().ok_or(LoadError::FileError)?;
|
||||
|
||||
let contents = storage
|
||||
.get_item("state")
|
||||
.map_err(|_| LoadError::FileError)?
|
||||
.ok_or(LoadError::FileError)?;
|
||||
|
||||
serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
|
||||
}
|
||||
|
||||
async fn save(self) -> Result<(), SaveError> {
|
||||
let storage = Self::storage().ok_or(SaveError::FileError)?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self)
|
||||
.map_err(|_| SaveError::FormatError)?;
|
||||
|
||||
storage
|
||||
.set_item("state", &json)
|
||||
.map_err(|_| SaveError::WriteError)?;
|
||||
|
||||
let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod style {
|
||||
use iced::{button, Background, Color, Vector};
|
||||
|
||||
|
@ -8,6 +8,3 @@ publish = false
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["image", "debug"] }
|
||||
env_logger = "0.7"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = "0.2.51"
|
||||
|
@ -779,16 +779,3 @@ mod style {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should be gracefully handled by Iced in the future. Probably using our
|
||||
// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at
|
||||
// some point.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
super::main()
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,12 @@ log = "0.4"
|
||||
[dependencies.futures]
|
||||
version = "0.3"
|
||||
|
||||
[dependencies.tokio]
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
features = ["rt-core"]
|
||||
features = ["rt-core", "rt-threaded"]
|
||||
|
||||
[dependencies.async-std]
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
|
||||
|
@ -1,100 +1,11 @@
|
||||
use futures::future::{BoxFuture, Future, FutureExt};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod native;
|
||||
|
||||
/// A collection of async operations.
|
||||
///
|
||||
/// You should be able to turn a future easily into a [`Command`], either by
|
||||
/// using the `From` trait or [`Command::perform`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub struct Command<T> {
|
||||
futures: Vec<BoxFuture<'static, T>>,
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use native::Command;
|
||||
|
||||
impl<T> Command<T> {
|
||||
/// Creates an empty [`Command`].
|
||||
///
|
||||
/// In other words, a [`Command`] that does nothing.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
futures: Vec::new(),
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static + Send,
|
||||
f: impl Fn(T) -> A + 'static + Send,
|
||||
) -> Command<A> {
|
||||
Command {
|
||||
futures: vec![future.map(f).boxed()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a transformation to the result of a [`Command`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn map<A>(
|
||||
mut self,
|
||||
f: impl Fn(T) -> A + 'static + Send + Sync,
|
||||
) -> Command<A>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let f = std::sync::Arc::new(f);
|
||||
|
||||
Command {
|
||||
futures: self
|
||||
.futures
|
||||
.drain(..)
|
||||
.map(|future| {
|
||||
let f = f.clone();
|
||||
|
||||
future.map(move |result| f(result)).boxed()
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the actions of all the given
|
||||
/// commands.
|
||||
///
|
||||
/// Once this command is run, all the commands will be exectued at once.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
|
||||
Self {
|
||||
futures: commands
|
||||
.into_iter()
|
||||
.flat_map(|command| command.futures)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`Command`] into its underlying list of futures.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn futures(self) -> Vec<BoxFuture<'static, T>> {
|
||||
self.futures
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A> From<A> for Command<T>
|
||||
where
|
||||
A: Future<Output = T> + 'static + Send,
|
||||
{
|
||||
fn from(future: A) -> Self {
|
||||
Self {
|
||||
futures: vec![future.boxed()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Command<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Command").finish()
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::Command;
|
||||
|
100
futures/src/command/native.rs
Normal file
100
futures/src/command/native.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use futures::future::{BoxFuture, Future, FutureExt};
|
||||
|
||||
/// A collection of async operations.
|
||||
///
|
||||
/// You should be able to turn a future easily into a [`Command`], either by
|
||||
/// using the `From` trait or [`Command::perform`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub struct Command<T> {
|
||||
futures: Vec<BoxFuture<'static, T>>,
|
||||
}
|
||||
|
||||
impl<T> Command<T> {
|
||||
/// Creates an empty [`Command`].
|
||||
///
|
||||
/// In other words, a [`Command`] that does nothing.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
futures: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static + Send,
|
||||
f: impl Fn(T) -> A + 'static + Send,
|
||||
) -> Command<A> {
|
||||
Command {
|
||||
futures: vec![future.map(f).boxed()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a transformation to the result of a [`Command`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn map<A>(
|
||||
mut self,
|
||||
f: impl Fn(T) -> A + 'static + Send + Sync,
|
||||
) -> Command<A>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let f = std::sync::Arc::new(f);
|
||||
|
||||
Command {
|
||||
futures: self
|
||||
.futures
|
||||
.drain(..)
|
||||
.map(|future| {
|
||||
let f = f.clone();
|
||||
|
||||
future.map(move |result| f(result)).boxed()
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the actions of all the given
|
||||
/// commands.
|
||||
///
|
||||
/// Once this command is run, all the commands will be executed at once.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
|
||||
Self {
|
||||
futures: commands
|
||||
.into_iter()
|
||||
.flat_map(|command| command.futures)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`Command`] into its underlying list of futures.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn futures(self) -> Vec<BoxFuture<'static, T>> {
|
||||
self.futures
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A> From<A> for Command<T>
|
||||
where
|
||||
A: Future<Output = T> + 'static + Send,
|
||||
{
|
||||
fn from(future: A) -> Self {
|
||||
Self {
|
||||
futures: vec![future.boxed()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Command<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Command").finish()
|
||||
}
|
||||
}
|
101
futures/src/command/web.rs
Normal file
101
futures/src/command/web.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use futures::future::{Future, FutureExt};
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A collection of async operations.
|
||||
///
|
||||
/// You should be able to turn a future easily into a [`Command`], either by
|
||||
/// using the `From` trait or [`Command::perform`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub struct Command<T> {
|
||||
futures: Vec<Pin<Box<dyn Future<Output = T> + 'static>>>,
|
||||
}
|
||||
|
||||
impl<T> Command<T> {
|
||||
/// Creates an empty [`Command`].
|
||||
///
|
||||
/// In other words, a [`Command`] that does nothing.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
futures: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static,
|
||||
f: impl Fn(T) -> A + 'static,
|
||||
) -> Command<A> {
|
||||
Command {
|
||||
futures: vec![future.map(f).boxed_local()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a transformation to the result of a [`Command`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn map<A>(
|
||||
mut self,
|
||||
f: impl Fn(T) -> A + 'static + Send + Sync + Unpin,
|
||||
) -> Command<A>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let f = std::sync::Arc::new(f);
|
||||
|
||||
Command {
|
||||
futures: self
|
||||
.futures
|
||||
.drain(..)
|
||||
.map(|future| {
|
||||
let f = f.clone();
|
||||
|
||||
future.map(move |result| f(result)).boxed_local()
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the actions of all the given
|
||||
/// commands.
|
||||
///
|
||||
/// Once this command is run, all the commands will be executed at once.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
|
||||
Self {
|
||||
futures: commands
|
||||
.into_iter()
|
||||
.flat_map(|command| command.futures)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`Command`] into its underlying list of futures.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn futures(self) -> Vec<Pin<Box<dyn Future<Output = T> + 'static>>> {
|
||||
self.futures
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A> From<A> for Command<T>
|
||||
where
|
||||
A: Future<Output = T> + 'static,
|
||||
{
|
||||
fn from(future: A) -> Self {
|
||||
Self {
|
||||
futures: vec![future.boxed_local()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Command<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Command").finish()
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
//! Choose your preferred executor to power a runtime.
|
||||
mod null;
|
||||
|
||||
#[cfg(feature = "thread-pool")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))]
|
||||
mod thread_pool;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
|
||||
mod tokio;
|
||||
|
||||
#[cfg(feature = "async-std")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||
mod async_std;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -15,13 +15,13 @@ mod wasm_bindgen;
|
||||
|
||||
pub use null::Null;
|
||||
|
||||
#[cfg(feature = "thread-pool")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))]
|
||||
pub use thread_pool::ThreadPool;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
|
||||
pub use self::tokio::Tokio;
|
||||
|
||||
#[cfg(feature = "async-std")]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||
pub use self::async_std::AsyncStd;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -41,8 +41,15 @@ pub trait Executor: Sized {
|
||||
/// Spawns a future in the [`Executor`].
|
||||
///
|
||||
/// [`Executor`]: trait.Executor.html
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static);
|
||||
|
||||
/// Spawns a local future in the [`Executor`].
|
||||
///
|
||||
/// [`Executor`]: trait.Executor.html
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn spawn(&self, future: impl Future<Output = ()> + 'static);
|
||||
|
||||
/// Runs the given closure inside the [`Executor`].
|
||||
///
|
||||
/// Some executors, like `tokio`, require some global state to be in place
|
||||
|
@ -11,5 +11,9 @@ impl Executor for Null {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn spawn(&self, _future: impl Future<Output = ()> + 'static) {}
|
||||
}
|
||||
|
@ -9,10 +9,7 @@ impl Executor for WasmBindgen {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
&self,
|
||||
future: impl futures::Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) {
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
}
|
||||
|
@ -73,11 +73,13 @@ where
|
||||
for future in futures {
|
||||
let mut sender = self.sender.clone();
|
||||
|
||||
self.executor.spawn(future.then(|message| async move {
|
||||
let future = future.then(|message| async move {
|
||||
let _ = sender.send(message).await;
|
||||
|
||||
()
|
||||
}));
|
||||
});
|
||||
|
||||
self.executor.spawn(future);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
//! Display images in your user interface.
|
||||
|
||||
use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
|
||||
|
||||
use std::{
|
||||
|
@ -9,6 +9,8 @@ use crate::{
|
||||
layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point,
|
||||
Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
/// A field that can be filled with text.
|
||||
@ -43,7 +45,7 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
|
||||
is_secure: bool,
|
||||
font: Font,
|
||||
width: Length,
|
||||
max_width: Length,
|
||||
max_width: u32,
|
||||
padding: u16,
|
||||
size: Option<u16>,
|
||||
on_change: Box<dyn Fn(String) -> Message>,
|
||||
@ -78,7 +80,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
|
||||
is_secure: false,
|
||||
font: Font::Default,
|
||||
width: Length::Fill,
|
||||
max_width: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
padding: 0,
|
||||
size: None,
|
||||
on_change: Box::new(on_change),
|
||||
@ -114,7 +116,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
|
||||
/// Sets the maximum width of the [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn max_width(mut self, max_width: Length) -> Self {
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
@ -178,6 +180,7 @@ where
|
||||
let limits = limits
|
||||
.pad(padding)
|
||||
.width(self.width)
|
||||
.max_width(self.max_width)
|
||||
.height(Length::Units(text_size));
|
||||
|
||||
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||
|
@ -5,36 +5,49 @@ pub use platform::Default;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod platform {
|
||||
use iced_winit::{executor::ThreadPool, futures, Executor};
|
||||
use iced_futures::{executor, futures};
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
type Executor = executor::Tokio;
|
||||
|
||||
#[cfg(all(not(feature = "tokio"), feature = "async-std"))]
|
||||
type Executor = executor::AsyncStd;
|
||||
|
||||
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
||||
type Executor = executor::ThreadPool;
|
||||
|
||||
/// A default cross-platform executor.
|
||||
///
|
||||
/// - On native platforms, it will use a `iced_futures::executor::ThreadPool`.
|
||||
/// - On native platforms, it will use:
|
||||
/// - `iced_futures::executor::Tokio` when the `tokio` feature is enabled.
|
||||
/// - `iced_futures::executor::AsyncStd` when the `async-std` feature is
|
||||
/// enabled.
|
||||
/// - `iced_futures::executor::ThreadPool` otherwise.
|
||||
/// - On the Web, it will use `iced_futures::executor::WasmBindgen`.
|
||||
#[derive(Debug)]
|
||||
pub struct Default(ThreadPool);
|
||||
pub struct Default(Executor);
|
||||
|
||||
impl Executor for Default {
|
||||
impl super::Executor for Default {
|
||||
fn new() -> Result<Self, futures::io::Error> {
|
||||
Ok(Default(ThreadPool::new()?))
|
||||
Ok(Default(Executor::new()?))
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
&self,
|
||||
future: impl futures::Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
self.0.spawn(future);
|
||||
let _ = self.0.spawn(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod platform {
|
||||
use iced_web::{executor::WasmBindgen, futures, Executor};
|
||||
use iced_futures::{executor::WasmBindgen, futures, Executor};
|
||||
|
||||
/// A default cross-platform executor.
|
||||
///
|
||||
/// - On native platforms, it will use a `iced_futures::executor::ThreadPool`.
|
||||
/// - On native platforms, it will use `iced_futures::executor::ThreadPool`.
|
||||
/// - On the Web, it will use `iced_futures::executor::WasmBindgen`.
|
||||
#[derive(Debug)]
|
||||
pub struct Default(WasmBindgen);
|
||||
@ -44,10 +57,7 @@ mod platform {
|
||||
Ok(Default(WasmBindgen::new()?))
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
&self,
|
||||
future: impl futures::Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) {
|
||||
self.0.spawn(future);
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ categories = ["web-programming"]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
iced_style = { version = "0.1.0-alpha", path = "../style" }
|
||||
dodrio = "0.1.0"
|
||||
wasm-bindgen = "0.2.51"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
url = "2.0"
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.1.0"
|
||||
@ -37,4 +39,5 @@ features = [
|
||||
"Event",
|
||||
"EventTarget",
|
||||
"InputEvent",
|
||||
"KeyboardEvent",
|
||||
]
|
||||
|
@ -1,11 +1,11 @@
|
||||
//! Style your widgets.
|
||||
use crate::{bumpalo, Align, Color, Length};
|
||||
use crate::{bumpalo, Align, Background, Color, Length};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// The style of a VDOM node.
|
||||
/// A CSS rule of a VDOM node.
|
||||
#[derive(Debug)]
|
||||
pub enum Style {
|
||||
pub enum Rule {
|
||||
/// Container with vertical distribution
|
||||
Column,
|
||||
|
||||
@ -19,16 +19,16 @@ pub enum Style {
|
||||
Spacing(u16),
|
||||
}
|
||||
|
||||
impl Style {
|
||||
impl Rule {
|
||||
/// Returns the class name of the [`Style`].
|
||||
///
|
||||
/// [`Style`]: enum.Style.html
|
||||
pub fn class<'a>(&self) -> String {
|
||||
match self {
|
||||
Style::Column => String::from("c"),
|
||||
Style::Row => String::from("r"),
|
||||
Style::Padding(padding) => format!("p-{}", padding),
|
||||
Style::Spacing(spacing) => format!("s-{}", spacing),
|
||||
Rule::Column => String::from("c"),
|
||||
Rule::Row => String::from("r"),
|
||||
Rule::Padding(padding) => format!("p-{}", padding),
|
||||
Rule::Spacing(spacing) => format!("s-{}", spacing),
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,24 +39,24 @@ impl Style {
|
||||
let class = self.class();
|
||||
|
||||
match self {
|
||||
Style::Column => {
|
||||
Rule::Column => {
|
||||
let body = "{ display: flex; flex-direction: column; }";
|
||||
|
||||
bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
|
||||
}
|
||||
Style::Row => {
|
||||
Rule::Row => {
|
||||
let body = "{ display: flex; flex-direction: row; }";
|
||||
|
||||
bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
|
||||
}
|
||||
Style::Padding(padding) => bumpalo::format!(
|
||||
Rule::Padding(padding) => bumpalo::format!(
|
||||
in bump,
|
||||
".{} {{ box-sizing: border-box; padding: {}px }}",
|
||||
class,
|
||||
padding
|
||||
)
|
||||
.into_bump_str(),
|
||||
Style::Spacing(spacing) => bumpalo::format!(
|
||||
Rule::Spacing(spacing) => bumpalo::format!(
|
||||
in bump,
|
||||
".c.{} > * {{ margin-bottom: {}px }} \
|
||||
.r.{} > * {{ margin-right: {}px }} \
|
||||
@ -74,34 +74,34 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// A sheet of styles.
|
||||
/// A cascading style sheet.
|
||||
#[derive(Debug)]
|
||||
pub struct Sheet<'a> {
|
||||
styles: BTreeMap<String, &'a str>,
|
||||
pub struct Css<'a> {
|
||||
rules: BTreeMap<String, &'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Sheet<'a> {
|
||||
impl<'a> Css<'a> {
|
||||
/// Creates an empty style [`Sheet`].
|
||||
///
|
||||
/// [`Sheet`]: struct.Sheet.html
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
styles: BTreeMap::new(),
|
||||
Css {
|
||||
rules: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts the [`Style`] in the [`Sheet`], if it was not previously
|
||||
/// Inserts the [`rule`] in the [`Sheet`], if it was not previously
|
||||
/// inserted.
|
||||
///
|
||||
/// It returns the class name of the provided [`Style`].
|
||||
/// It returns the class name of the provided [`Rule`].
|
||||
///
|
||||
/// [`Sheet`]: struct.Sheet.html
|
||||
/// [`Style`]: enum.Style.html
|
||||
pub fn insert(&mut self, bump: &'a bumpalo::Bump, style: Style) -> String {
|
||||
let class = style.class();
|
||||
/// [`Rule`]: enum.Rule.html
|
||||
pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String {
|
||||
let class = rule.class();
|
||||
|
||||
if !self.styles.contains_key(&class) {
|
||||
let _ = self.styles.insert(class.clone(), style.declaration(bump));
|
||||
if !self.rules.contains_key(&class) {
|
||||
let _ = self.rules.insert(class.clone(), rule.declaration(bump));
|
||||
}
|
||||
|
||||
class
|
||||
@ -119,12 +119,12 @@ impl<'a> Sheet<'a> {
|
||||
declarations.push(text(
|
||||
"body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }",
|
||||
));
|
||||
declarations.push(text("p { margin: 0 }"));
|
||||
declarations.push(text("* { margin: 0; padding: 0 }"));
|
||||
declarations.push(text(
|
||||
"button { border: none; cursor: pointer; outline: none }",
|
||||
));
|
||||
|
||||
for declaration in self.styles.values() {
|
||||
for declaration in self.rules.values() {
|
||||
declarations.push(text(*declaration));
|
||||
}
|
||||
|
||||
@ -143,6 +143,26 @@ pub fn length(length: Length) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style value for the given maximum length in units.
|
||||
pub fn max_length(units: u32) -> String {
|
||||
use std::u32;
|
||||
|
||||
if units == u32::MAX {
|
||||
String::from("initial")
|
||||
} else {
|
||||
format!("{}px", units)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style value for the given minimum length in units.
|
||||
pub fn min_length(units: u32) -> String {
|
||||
if units == 0 {
|
||||
String::from("initial")
|
||||
} else {
|
||||
format!("{}px", units)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style value for the given [`Color`].
|
||||
///
|
||||
/// [`Color`]: ../struct.Color.html
|
||||
@ -150,6 +170,15 @@ pub fn color(Color { r, g, b, a }: Color) -> String {
|
||||
format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a)
|
||||
}
|
||||
|
||||
/// Returns the style value for the given [`Background`].
|
||||
///
|
||||
/// [`Background`]: ../struct.Background.html
|
||||
pub fn background(background: Background) -> String {
|
||||
match background {
|
||||
Background::Color(c) => color(c),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style value for the given [`Align`].
|
||||
///
|
||||
/// [`Align`]: ../enum.Align.html
|
@ -1,4 +1,4 @@
|
||||
use crate::{style, Bus, Color, Widget};
|
||||
use crate::{Bus, Color, Css, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::rc::Rc;
|
||||
@ -57,7 +57,7 @@ impl<'a, Message> Element<'a, Message> {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
self.widget.node(bump, bus, style_sheet)
|
||||
}
|
||||
@ -89,7 +89,7 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<B>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
self.widget
|
||||
.node(bump, &bus.map(self.mapper.clone()), style_sheet)
|
||||
|
@ -63,11 +63,12 @@ mod bus;
|
||||
mod element;
|
||||
mod hasher;
|
||||
|
||||
pub mod style;
|
||||
pub mod css;
|
||||
pub mod subscription;
|
||||
pub mod widget;
|
||||
|
||||
pub use bus::Bus;
|
||||
pub use css::Css;
|
||||
pub use dodrio;
|
||||
pub use element::Element;
|
||||
pub use hasher::Hasher;
|
||||
@ -76,7 +77,6 @@ pub use iced_core::{
|
||||
VerticalAlignment,
|
||||
};
|
||||
pub use iced_futures::{executor, futures, Command};
|
||||
pub use style::Style;
|
||||
pub use subscription::Subscription;
|
||||
|
||||
#[doc(no_inline)]
|
||||
@ -241,13 +241,13 @@ where
|
||||
|
||||
let mut ui = self.application.borrow_mut();
|
||||
let element = ui.view();
|
||||
let mut style_sheet = style::Sheet::new();
|
||||
let mut css = Css::new();
|
||||
|
||||
let node = element.widget.node(bump, &self.bus, &mut style_sheet);
|
||||
let node = element.widget.node(bump, &self.bus, &mut css);
|
||||
|
||||
div(bump)
|
||||
.attr("style", "width: 100%; height: 100%")
|
||||
.children(vec![style_sheet.node(bump), node])
|
||||
.children(vec![css.node(bump), node])
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,20 @@
|
||||
//! ```
|
||||
//!
|
||||
//! [`Widget`]: trait.Widget.html
|
||||
use crate::{style, Bus};
|
||||
use crate::{Bus, Css};
|
||||
use dodrio::bumpalo;
|
||||
|
||||
pub mod button;
|
||||
pub mod checkbox;
|
||||
pub mod container;
|
||||
pub mod image;
|
||||
pub mod progress_bar;
|
||||
pub mod radio;
|
||||
pub mod scrollable;
|
||||
pub mod slider;
|
||||
pub mod text_input;
|
||||
|
||||
mod checkbox;
|
||||
mod column;
|
||||
mod container;
|
||||
mod image;
|
||||
mod radio;
|
||||
mod row;
|
||||
mod space;
|
||||
mod text;
|
||||
@ -46,6 +47,7 @@ pub use checkbox::Checkbox;
|
||||
pub use column::Column;
|
||||
pub use container::Container;
|
||||
pub use image::Image;
|
||||
pub use progress_bar::ProgressBar;
|
||||
pub use radio::Radio;
|
||||
pub use row::Row;
|
||||
pub use space::Space;
|
||||
@ -64,6 +66,6 @@ pub trait Widget<Message> {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
_bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b>;
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
//!
|
||||
//! [`Button`]: struct.Button.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::{style, Background, Bus, Element, Length, Style, Widget};
|
||||
use crate::{css, Background, Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::button::{Style, StyleSheet};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
|
||||
@ -26,10 +28,11 @@ pub struct Button<'a, Message> {
|
||||
content: Element<'a, Message>,
|
||||
on_press: Option<Message>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
min_width: u32,
|
||||
min_height: u32,
|
||||
padding: u16,
|
||||
background: Option<Background>,
|
||||
border_radius: u16,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Button<'a, Message> {
|
||||
@ -46,10 +49,11 @@ impl<'a, Message> Button<'a, Message> {
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
min_width: 0,
|
||||
padding: 0,
|
||||
background: None,
|
||||
border_radius: 0,
|
||||
min_height: 0,
|
||||
padding: 5,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +65,14 @@ impl<'a, Message> Button<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the minimum width of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
@ -69,6 +81,14 @@ impl<'a, Message> Button<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the minimum height of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn min_height(mut self, min_height: u32) -> Self {
|
||||
self.min_height = min_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
@ -77,20 +97,11 @@ impl<'a, Message> Button<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Background`] of the [`Button`].
|
||||
/// Sets the style of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
/// [`Background`]: ../../struct.Background.html
|
||||
pub fn background<T: Into<Background>>(mut self, background: T) -> Self {
|
||||
self.background = Some(background.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the border radius of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn border_radius(mut self, border_radius: u16) -> Self {
|
||||
self.border_radius = border_radius;
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
@ -126,18 +137,20 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let width = style::length(self.width);
|
||||
let padding_class =
|
||||
style_sheet.insert(bump, Style::Padding(self.padding));
|
||||
// TODO: State-based styling
|
||||
let style = self.style.active();
|
||||
|
||||
let background = match self.background {
|
||||
let padding_class =
|
||||
style_sheet.insert(bump, css::Rule::Padding(self.padding));
|
||||
|
||||
let background = match style.background {
|
||||
None => String::from("none"),
|
||||
Some(background) => match background {
|
||||
Background::Color(color) => style::color(color),
|
||||
Background::Color(color) => css::color(color),
|
||||
},
|
||||
};
|
||||
|
||||
@ -150,11 +163,12 @@ where
|
||||
"style",
|
||||
bumpalo::format!(
|
||||
in bump,
|
||||
"background: {}; border-radius: {}px; width:{}; min-width: {}px",
|
||||
"background: {}; border-radius: {}px; width:{}; min-width: {}; color: {}",
|
||||
background,
|
||||
self.border_radius,
|
||||
width,
|
||||
self.min_width
|
||||
style.border_radius,
|
||||
css::length(self.width),
|
||||
css::min_length(self.min_width),
|
||||
css::color(style.text_color)
|
||||
)
|
||||
.into_bump_str(),
|
||||
)
|
||||
@ -168,8 +182,6 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Complete styling
|
||||
|
||||
node.finish()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::{style, Bus, Color, Element, Widget};
|
||||
//! Show toggle controls using checkboxes.
|
||||
use crate::{css, Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::checkbox::{Style, StyleSheet};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::rc::Rc;
|
||||
@ -25,7 +28,8 @@ pub struct Checkbox<Message> {
|
||||
is_checked: bool,
|
||||
on_toggle: Rc<dyn Fn(bool) -> Message>,
|
||||
label: String,
|
||||
label_color: Option<Color>,
|
||||
width: Length,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<Message> Checkbox<Message> {
|
||||
@ -47,15 +51,24 @@ impl<Message> Checkbox<Message> {
|
||||
is_checked,
|
||||
on_toggle: Rc::new(f),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
width: Length::Shrink,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the color of the label of the [`Checkbox`].
|
||||
/// Sets the width of the [`Checkbox`].
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Checkbox`].
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -68,7 +81,7 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
@ -78,9 +91,23 @@ where
|
||||
let on_toggle = self.on_toggle.clone();
|
||||
let is_checked = self.is_checked;
|
||||
|
||||
// TODO: Complete styling
|
||||
let row_class = style_sheet.insert(bump, css::Rule::Row);
|
||||
|
||||
let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5));
|
||||
|
||||
label(bump)
|
||||
.attr(
|
||||
"class",
|
||||
bumpalo::format!(in bump, "{} {}", row_class, spacing_class)
|
||||
.into_bump_str(),
|
||||
)
|
||||
.attr(
|
||||
"style",
|
||||
bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width))
|
||||
.into_bump_str(),
|
||||
)
|
||||
.children(vec![
|
||||
// TODO: Checkbox styling
|
||||
input(bump)
|
||||
.attr("type", "checkbox")
|
||||
.bool_attr("checked", self.is_checked)
|
||||
@ -91,7 +118,8 @@ where
|
||||
vdom.schedule_render();
|
||||
})
|
||||
.finish(),
|
||||
text(checkbox_label.into_bump_str()),
|
||||
span(bump).children(vec![
|
||||
text(checkbox_label.into_bump_str())]).finish(),
|
||||
])
|
||||
.finish()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{style, Align, Bus, Element, Length, Style, Widget};
|
||||
use crate::{css, Align, Bus, Css, Element, Length, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::u32;
|
||||
@ -28,7 +28,7 @@ impl<'a, Message> Column<'a, Message> {
|
||||
Column {
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
width: Length::Shrink,
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
@ -112,7 +112,7 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
publish: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
@ -122,18 +122,13 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> {
|
||||
.map(|element| element.widget.node(bump, publish, style_sheet))
|
||||
.collect();
|
||||
|
||||
let column_class = style_sheet.insert(bump, Style::Column);
|
||||
let column_class = style_sheet.insert(bump, css::Rule::Column);
|
||||
|
||||
let spacing_class =
|
||||
style_sheet.insert(bump, Style::Spacing(self.spacing));
|
||||
style_sheet.insert(bump, css::Rule::Spacing(self.spacing));
|
||||
|
||||
let padding_class =
|
||||
style_sheet.insert(bump, Style::Padding(self.padding));
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
|
||||
let align_items = style::align(self.align_items);
|
||||
style_sheet.insert(bump, css::Rule::Padding(self.padding));
|
||||
|
||||
// TODO: Complete styling
|
||||
div(bump)
|
||||
@ -144,12 +139,12 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> {
|
||||
)
|
||||
.attr("style", bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; height: {}; max-width: {}px; max-height: {}px; align-items: {}",
|
||||
width,
|
||||
height,
|
||||
self.max_width,
|
||||
self.max_height,
|
||||
align_items
|
||||
"width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}",
|
||||
css::length(self.width),
|
||||
css::length(self.height),
|
||||
css::max_length(self.max_width),
|
||||
css::max_length(self.max_height),
|
||||
css::align(self.align_items)
|
||||
).into_bump_str()
|
||||
)
|
||||
.children(children)
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::{bumpalo, style, Align, Bus, Element, Length, Style, Widget};
|
||||
//! Decorate content and apply alignment.
|
||||
use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::container::{Style, StyleSheet};
|
||||
|
||||
/// An element decorating some content.
|
||||
///
|
||||
@ -11,6 +14,7 @@ pub struct Container<'a, Message> {
|
||||
max_height: u32,
|
||||
horizontal_alignment: Align,
|
||||
vertical_alignment: Align,
|
||||
style_sheet: Box<dyn StyleSheet>,
|
||||
content: Element<'a, Message>,
|
||||
}
|
||||
|
||||
@ -31,6 +35,7 @@ impl<'a, Message> Container<'a, Message> {
|
||||
max_height: u32::MAX,
|
||||
horizontal_alignment: Align::Start,
|
||||
vertical_alignment: Align::Start,
|
||||
style_sheet: Default::default(),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
@ -84,6 +89,14 @@ impl<'a, Message> Container<'a, Message> {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style_sheet = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message> for Container<'a, Message>
|
||||
@ -94,17 +107,13 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let column_class = style_sheet.insert(bump, Style::Column);
|
||||
let column_class = style_sheet.insert(bump, css::Rule::Column);
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
|
||||
let align_items = style::align(self.horizontal_alignment);
|
||||
let justify_content = style::align(self.vertical_alignment);
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
let node = div(bump)
|
||||
.attr(
|
||||
@ -115,12 +124,17 @@ where
|
||||
"style",
|
||||
bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; height: {}; max-width: {}px; align-items: {}; justify-content: {}",
|
||||
width,
|
||||
height,
|
||||
self.max_width,
|
||||
align_items,
|
||||
justify_content
|
||||
"width: {}; height: {}; max-width: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px",
|
||||
css::length(self.width),
|
||||
css::length(self.height),
|
||||
css::max_length(self.max_width),
|
||||
css::align(self.horizontal_alignment),
|
||||
css::align(self.vertical_alignment),
|
||||
style.background.map(css::background).unwrap_or(String::from("initial")),
|
||||
style.text_color.map(css::color).unwrap_or(String::from("inherit")),
|
||||
style.border_width,
|
||||
css::color(style.border_color),
|
||||
style.border_radius
|
||||
)
|
||||
.into_bump_str(),
|
||||
)
|
||||
|
@ -1,6 +1,12 @@
|
||||
use crate::{style, Bus, Element, Length, Widget};
|
||||
//! Display images in your user interface.
|
||||
use crate::{Bus, Css, Element, Hasher, Length, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::{
|
||||
hash::{Hash, Hasher as _},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A frame that displays an image while keeping aspect ratio.
|
||||
///
|
||||
@ -14,7 +20,7 @@ use dodrio::bumpalo;
|
||||
#[derive(Debug)]
|
||||
pub struct Image {
|
||||
/// The image path
|
||||
pub path: String,
|
||||
pub handle: Handle,
|
||||
|
||||
/// The width of the image
|
||||
pub width: Length,
|
||||
@ -27,9 +33,9 @@ impl Image {
|
||||
/// Creates a new [`Image`] with the given path.
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
pub fn new<T: Into<String>>(path: T) -> Self {
|
||||
pub fn new<T: Into<Handle>>(handle: T) -> Self {
|
||||
Image {
|
||||
path: path.into(),
|
||||
handle: handle.into(),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
@ -57,11 +63,13 @@ impl<Message> Widget<Message> for Image {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
_bus: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
_style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let src = bumpalo::format!(in bump, "{}", self.path);
|
||||
let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() {
|
||||
Data::Path(path) => path.to_str().unwrap_or("")
|
||||
});
|
||||
|
||||
let mut image = img(bump).attr("src", src.into_bump_str());
|
||||
|
||||
@ -89,3 +97,74 @@ impl<'a, Message> From<Image> for Element<'a, Message> {
|
||||
Element::new(image)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Image`] handle.
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle {
|
||||
id: u64,
|
||||
data: Arc<Data>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Creates an image [`Handle`] pointing to the image of the given path.
|
||||
///
|
||||
/// [`Handle`]: struct.Handle.html
|
||||
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
|
||||
Self::from_data(Data::Path(path.into()))
|
||||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
let mut hasher = Hasher::default();
|
||||
data.hash(&mut hasher);
|
||||
|
||||
Handle {
|
||||
id: hasher.finish(),
|
||||
data: Arc::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique identifier of the [`Handle`].
|
||||
///
|
||||
/// [`Handle`]: struct.Handle.html
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns a reference to the image [`Data`].
|
||||
///
|
||||
/// [`Data`]: enum.Data.html
|
||||
pub fn data(&self) -> &Data {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Handle {
|
||||
fn from(path: String) -> Handle {
|
||||
Handle::from_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Handle {
|
||||
fn from(path: &str) -> Handle {
|
||||
Handle::from_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of an [`Image`].
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Data {
|
||||
/// A remote image
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
web/src/widget/progress_bar.rs
Normal file
124
web/src/widget/progress_bar.rs
Normal file
@ -0,0 +1,124 @@
|
||||
//! Provide progress feedback to your users.
|
||||
use crate::{bumpalo, css, Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::progress_bar::{Style, StyleSheet};
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A bar that displays progress.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use iced_web::ProgressBar;
|
||||
///
|
||||
/// let value = 50.0;
|
||||
///
|
||||
/// ProgressBar::new(0.0..=100.0, value);
|
||||
/// ```
|
||||
///
|
||||
/// ![Progress bar](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct ProgressBar {
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
width: Length,
|
||||
height: Option<Length>,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl ProgressBar {
|
||||
/// Creates a new [`ProgressBar`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * an inclusive range of possible values
|
||||
/// * the current value of the [`ProgressBar`]
|
||||
///
|
||||
/// [`ProgressBar`]: struct.ProgressBar.html
|
||||
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
|
||||
ProgressBar {
|
||||
value: value.max(*range.start()).min(*range.end()),
|
||||
range,
|
||||
width: Length::Fill,
|
||||
height: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`ProgressBar`].
|
||||
///
|
||||
/// [`ProgressBar`]: struct.ProgressBar.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`ProgressBar`].
|
||||
///
|
||||
/// [`ProgressBar`]: struct.ProgressBar.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`ProgressBar`].
|
||||
///
|
||||
/// [`ProgressBar`]: struct.ProgressBar.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> Widget<Message> for ProgressBar {
|
||||
fn node<'b>(
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
_bus: &Bus<Message>,
|
||||
_style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let (range_start, range_end) = self.range.clone().into_inner();
|
||||
let amount_filled =
|
||||
(self.value - range_start) / (range_end - range_start).max(1.0);
|
||||
|
||||
let style = self.style.style();
|
||||
|
||||
let bar = div(bump)
|
||||
.attr(
|
||||
"style",
|
||||
bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}%; height: 100%; background: {}",
|
||||
amount_filled * 100.0,
|
||||
css::background(style.bar)
|
||||
)
|
||||
.into_bump_str(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let node = div(bump).attr(
|
||||
"style",
|
||||
bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; height: {}; background: {}; border-radius: {}px; overflow: hidden;",
|
||||
css::length(self.width),
|
||||
css::length(self.height.unwrap_or(Length::Units(30))),
|
||||
css::background(style.background),
|
||||
style.border_radius
|
||||
)
|
||||
.into_bump_str(),
|
||||
).children(vec![bar]);
|
||||
|
||||
node.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<ProgressBar> for Element<'a, Message>
|
||||
where
|
||||
Message: 'static,
|
||||
{
|
||||
fn from(container: ProgressBar) -> Element<'a, Message> {
|
||||
Element::new(container)
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
use crate::{style, Bus, Color, Element, Widget};
|
||||
//! Create choices using radio buttons.
|
||||
use crate::{Bus, Css, Element, Widget};
|
||||
|
||||
pub use iced_style::radio::{Style, StyleSheet};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
|
||||
@ -32,7 +35,7 @@ pub struct Radio<Message> {
|
||||
is_selected: bool,
|
||||
on_click: Message,
|
||||
label: String,
|
||||
label_color: Option<Color>,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<Message> Radio<Message> {
|
||||
@ -55,15 +58,15 @@ impl<Message> Radio<Message> {
|
||||
is_selected: Some(value) == selected,
|
||||
on_click: f(value),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the `Color` of the label of the [`Radio`].
|
||||
/// Sets the style of the [`Radio`] button.
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -76,7 +79,7 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
_style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{style, Align, Bus, Element, Length, Style, Widget};
|
||||
use crate::{css, Align, Bus, Css, Element, Length, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::u32;
|
||||
@ -28,7 +28,7 @@ impl<'a, Message> Row<'a, Message> {
|
||||
Row {
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
width: Length::Shrink,
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
@ -113,7 +113,7 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
publish: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
@ -123,18 +123,13 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> {
|
||||
.map(|element| element.widget.node(bump, publish, style_sheet))
|
||||
.collect();
|
||||
|
||||
let row_class = style_sheet.insert(bump, Style::Row);
|
||||
let row_class = style_sheet.insert(bump, css::Rule::Row);
|
||||
|
||||
let spacing_class =
|
||||
style_sheet.insert(bump, Style::Spacing(self.spacing));
|
||||
style_sheet.insert(bump, css::Rule::Spacing(self.spacing));
|
||||
|
||||
let padding_class =
|
||||
style_sheet.insert(bump, Style::Padding(self.padding));
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
|
||||
let justify_content = style::align(self.align_items);
|
||||
style_sheet.insert(bump, css::Rule::Padding(self.padding));
|
||||
|
||||
// TODO: Complete styling
|
||||
div(bump)
|
||||
@ -145,12 +140,12 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> {
|
||||
)
|
||||
.attr("style", bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; height: {}; max-width: {}px; max-height: {}px; justify-content: {}",
|
||||
width,
|
||||
height,
|
||||
self.max_width,
|
||||
self.max_height,
|
||||
justify_content
|
||||
"width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}",
|
||||
css::length(self.width),
|
||||
css::length(self.height),
|
||||
css::max_length(self.max_width),
|
||||
css::max_length(self.max_height),
|
||||
css::align(self.align_items)
|
||||
).into_bump_str()
|
||||
)
|
||||
.children(children)
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Navigate an endless amount of content with a scrollbar.
|
||||
use crate::{bumpalo, style, Align, Bus, Column, Element, Length, Widget};
|
||||
use crate::{bumpalo, css, Align, Bus, Column, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||
|
||||
/// A widget that can vertically display an infinite amount of content with a
|
||||
/// scrollbar.
|
||||
@ -9,6 +11,7 @@ pub struct Scrollable<'a, Message> {
|
||||
height: Length,
|
||||
max_height: u32,
|
||||
content: Column<'a, Message>,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Scrollable<'a, Message> {
|
||||
@ -24,6 +27,7 @@ impl<'a, Message> Scrollable<'a, Message> {
|
||||
height: Length::Shrink,
|
||||
max_height: u32::MAX,
|
||||
content: Column::new(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +89,14 @@ impl<'a, Message> Scrollable<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Scrollable`] .
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
@ -105,12 +117,14 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
let width = css::length(self.width);
|
||||
let height = css::length(self.height);
|
||||
|
||||
// TODO: Scrollbar styling
|
||||
|
||||
let node = div(bump)
|
||||
.attr(
|
||||
@ -126,8 +140,6 @@ where
|
||||
)
|
||||
.children(vec![self.content.node(bump, bus, style_sheet)]);
|
||||
|
||||
// TODO: Complete styling
|
||||
|
||||
node.finish()
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::{style, Bus, Element, Length, Widget};
|
||||
use crate::{Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::{ops::RangeInclusive, rc::Rc};
|
||||
@ -38,6 +40,7 @@ pub struct Slider<'a, Message> {
|
||||
value: f32,
|
||||
on_change: Rc<Box<dyn Fn(f32) -> Message>>,
|
||||
width: Length,
|
||||
style: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Slider<'a, Message> {
|
||||
@ -68,6 +71,7 @@ impl<'a, Message> Slider<'a, Message> {
|
||||
range,
|
||||
on_change: Rc::new(Box::new(on_change)),
|
||||
width: Length::Fill,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +82,14 @@ impl<'a, Message> Slider<'a, Message> {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message> for Slider<'a, Message>
|
||||
@ -88,7 +100,7 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
_style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
@ -103,7 +115,7 @@ where
|
||||
let event_bus = bus.clone();
|
||||
|
||||
// TODO: Make `step` configurable
|
||||
// TODO: Complete styling
|
||||
// TODO: Styling
|
||||
input(bump)
|
||||
.attr("type", "range")
|
||||
.attr("step", "0.01")
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{style, Bus, Element, Length, Widget};
|
||||
use crate::{css, Bus, Css, Element, Length, Widget};
|
||||
use dodrio::bumpalo;
|
||||
|
||||
/// An amount of empty space.
|
||||
@ -44,12 +44,12 @@ impl<'a, Message> Widget<Message> for Space {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
_publish: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
_css: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
let width = css::length(self.width);
|
||||
let height = css::length(self.height);
|
||||
|
||||
let style = bumpalo::format!(
|
||||
in bump,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
style, Bus, Color, Element, Font, HorizontalAlignment, Length,
|
||||
css, Bus, Color, Css, Element, Font, HorizontalAlignment, Length,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
use dodrio::bumpalo;
|
||||
@ -112,15 +112,18 @@ impl<'a, Message> Widget<Message> for Text {
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
_publish: &Bus<Message>,
|
||||
_style_sheet: &mut style::Sheet<'b>,
|
||||
_style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
|
||||
let content = bumpalo::format!(in bump, "{}", self.content);
|
||||
let color = style::color(self.color.unwrap_or(Color::BLACK));
|
||||
let color = self
|
||||
.color
|
||||
.map(css::color)
|
||||
.unwrap_or(String::from("inherit"));
|
||||
|
||||
let width = style::length(self.width);
|
||||
let height = style::length(self.height);
|
||||
let width = css::length(self.width);
|
||||
let height = css::length(self.height);
|
||||
|
||||
let text_align = match self.horizontal_alignment {
|
||||
HorizontalAlignment::Left => "left",
|
||||
@ -130,12 +133,16 @@ impl<'a, Message> Widget<Message> for Text {
|
||||
|
||||
let style = bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; height: {}; font-size: {}px; color: {}; text-align: {}",
|
||||
"width: {}; height: {}; font-size: {}px; color: {}; text-align: {}; font-family: {}",
|
||||
width,
|
||||
height,
|
||||
self.size.unwrap_or(20),
|
||||
color,
|
||||
text_align
|
||||
text_align,
|
||||
match self.font {
|
||||
Font::Default => "inherit",
|
||||
Font::External { name, .. } => name,
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Complete styling
|
||||
|
@ -4,8 +4,11 @@
|
||||
//!
|
||||
//! [`TextInput`]: struct.TextInput.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::{bumpalo, style, Bus, Element, Length, Style, Widget};
|
||||
use std::rc::Rc;
|
||||
use crate::{bumpalo, css, Bus, Css, Element, Length, Widget};
|
||||
|
||||
pub use iced_style::text_input::{Style, StyleSheet};
|
||||
|
||||
use std::{rc::Rc, u32};
|
||||
|
||||
/// A field that can be filled with text.
|
||||
///
|
||||
@ -34,11 +37,12 @@ pub struct TextInput<'a, Message> {
|
||||
value: String,
|
||||
is_secure: bool,
|
||||
width: Length,
|
||||
max_width: Length,
|
||||
max_width: u32,
|
||||
padding: u16,
|
||||
size: Option<u16>,
|
||||
on_change: Rc<Box<dyn Fn(String) -> Message>>,
|
||||
on_submit: Option<Message>,
|
||||
style_sheet: Box<dyn StyleSheet>,
|
||||
}
|
||||
|
||||
impl<'a, Message> TextInput<'a, Message> {
|
||||
@ -67,11 +71,12 @@ impl<'a, Message> TextInput<'a, Message> {
|
||||
value: String::from(value),
|
||||
is_secure: false,
|
||||
width: Length::Fill,
|
||||
max_width: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
padding: 0,
|
||||
size: None,
|
||||
on_change: Rc::new(Box::new(on_change)),
|
||||
on_submit: None,
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +99,7 @@ impl<'a, Message> TextInput<'a, Message> {
|
||||
/// Sets the maximum width of the [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn max_width(mut self, max_width: Length) -> Self {
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
@ -123,6 +128,14 @@ impl<'a, Message> TextInput<'a, Message> {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
|
||||
self.style_sheet = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message> for TextInput<'a, Message>
|
||||
@ -133,18 +146,19 @@ where
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
style_sheet: &mut style::Sheet<'b>,
|
||||
style_sheet: &mut Css<'b>,
|
||||
) -> dodrio::Node<'b> {
|
||||
use dodrio::builder::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let width = style::length(self.width);
|
||||
let max_width = style::length(self.max_width);
|
||||
let padding_class =
|
||||
style_sheet.insert(bump, Style::Padding(self.padding));
|
||||
style_sheet.insert(bump, css::Rule::Padding(self.padding));
|
||||
|
||||
let on_change = self.on_change.clone();
|
||||
let event_bus = bus.clone();
|
||||
let on_submit = self.on_submit.clone();
|
||||
let input_event_bus = bus.clone();
|
||||
let submit_event_bus = bus.clone();
|
||||
let style = self.style_sheet.active();
|
||||
|
||||
input(bump)
|
||||
.attr(
|
||||
@ -155,10 +169,15 @@ where
|
||||
"style",
|
||||
bumpalo::format!(
|
||||
in bump,
|
||||
"width: {}; max-width: {}; font-size: {}px",
|
||||
width,
|
||||
max_width,
|
||||
self.size.unwrap_or(20)
|
||||
"width: {}; max-width: {}; font-size: {}px; background: {}; border-width: {}px; border-color: {}; border-radius: {}px; color: {}",
|
||||
css::length(self.width),
|
||||
css::max_length(self.max_width),
|
||||
self.size.unwrap_or(20),
|
||||
css::background(style.background),
|
||||
style.border_width,
|
||||
css::color(style.border_color),
|
||||
style.border_radius,
|
||||
css::color(self.style_sheet.value_color())
|
||||
)
|
||||
.into_bump_str(),
|
||||
)
|
||||
@ -183,7 +202,17 @@ where
|
||||
Some(text_input) => text_input,
|
||||
};
|
||||
|
||||
event_bus.publish(on_change(text_input.value()));
|
||||
input_event_bus.publish(on_change(text_input.value()));
|
||||
})
|
||||
.on("keypress", move |_root, _vdom, event| {
|
||||
if let Some(on_submit) = on_submit.clone() {
|
||||
let event = event.unchecked_into::<web_sys::KeyboardEvent>();
|
||||
|
||||
match event.key_code() {
|
||||
13 => { submit_event_bus.publish(on_submit); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.finish()
|
||||
}
|
||||
@ -211,4 +240,12 @@ impl State {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn focused() -> Self {
|
||||
// TODO
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user