Merge pull request #180 from hecrj/feature/web-styling

Custom styling for `iced_web`
This commit is contained in:
Héctor Ramón 2020-02-06 10:21:52 -06:00 committed by GitHub
commit 97c308076f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 884 additions and 343 deletions

View File

@ -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" }

View File

@ -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"]

View File

@ -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
}

View File

@ -104,6 +104,7 @@ impl Sandbox for Styling {
"Toggle me!",
Message::CheckboxToggled,
)
.width(Length::Fill)
.style(self.theme);
let content = Column::new()

View File

@ -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"],

View File

@ -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};

View File

@ -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"

View File

@ -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()
}
}

View File

@ -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

View File

@ -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;

View 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
View 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()
}
}

View File

@ -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

View File

@ -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) {}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,5 +1,4 @@
//! Display images in your user interface.
use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
use std::{

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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",
]

View File

@ -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

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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>;
}

View File

@ -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()
}
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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(),
)

View File

@ -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),
}
}
}

View 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)
}
}

View File

@ -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::*;

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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")

View File

@ -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,

View File

@ -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

View File

@ -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()
}
}