Skip to content

Commit

Permalink
Merge pull request #1284 from iced-rs/virtual-widgets
Browse files Browse the repository at this point in the history
Stateless widgets
  • Loading branch information
hecrj authored Mar 23, 2022
2 parents 4aece6b + ef4c79e commit 0eef527
Show file tree
Hide file tree
Showing 140 changed files with 12,597 additions and 2,573 deletions.
25 changes: 17 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@ resolver = "2"

[features]
default = ["wgpu"]
# Enables the `iced_wgpu` renderer
wgpu = ["iced_wgpu"]
# Enables the `Image` widget
image = ["iced_wgpu/image"]
# Enables the `Svg` widget
svg = ["iced_wgpu/svg"]
# Enables the `Canvas` widget
canvas = ["iced_wgpu/canvas"]
canvas = ["iced_graphics/canvas"]
# Enables the `QRCode` widget
qr_code = ["iced_wgpu/qr_code"]
qr_code = ["iced_graphics/qr_code"]
# Enables the `iced_wgpu` renderer
wgpu = ["iced_wgpu"]
# Enables using system fonts
default_system_font = ["iced_wgpu/default_system_font"]
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
glow = ["iced_glow", "iced_glutin"]
# Enables the `Canvas` widget for `iced_glow`
glow_canvas = ["iced_glow/canvas"]
# Enables the `QRCode` widget for `iced_glow`
glow_qr_code = ["iced_glow/qr_code"]
# Enables using system fonts for `iced_glow`
glow_default_system_font = ["iced_glow/default_system_font"]
# Enables a debug view in native platforms (press F12)
Expand All @@ -44,6 +40,8 @@ async-std = ["iced_futures/async-std"]
smol = ["iced_futures/smol"]
# Enables advanced color conversion via `palette`
palette = ["iced_core/palette"]
# Enables pure, virtual widgets in the `pure` module
pure = ["iced_pure", "iced_graphics/pure"]

[badges]
maintenance = { status = "actively-developed" }
Expand All @@ -57,6 +55,7 @@ members = [
"glutin",
"lazy",
"native",
"pure",
"style",
"wgpu",
"winit",
Expand Down Expand Up @@ -87,15 +86,25 @@ members = [
"examples/tooltip",
"examples/tour",
"examples/url_handler",
"examples/pure/component",
"examples/pure/counter",
"examples/pure/game_of_life",
"examples/pure/pane_grid",
"examples/pure/pick_list",
"examples/pure/todos",
"examples/pure/tour",
"examples/websocket",
]

[dependencies]
iced_core = { version = "0.4", path = "core" }
iced_futures = { version = "0.3", path = "futures" }
iced_native = { version = "0.4", path = "native" }
iced_graphics = { version = "0.2", path = "graphics" }
iced_winit = { version = "0.3", path = "winit" }
iced_glutin = { version = "0.2", path = "glutin", optional = true }
iced_glow = { version = "0.2", path = "glow", optional = true }
iced_pure = { version = "0.1", path = "pure", optional = true }
thiserror = "1.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion examples/clock/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Application for Clock {
}
}

impl canvas::Program<Message> for Clock {
impl<Message> canvas::Program<Message> for Clock {
fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
let clock = self.clock.draw(bounds.size(), |frame| {
let center = frame.center();
Expand Down
2 changes: 1 addition & 1 deletion examples/color_palette/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ impl Theme {
}
}

impl canvas::Program<Message> for Theme {
impl<Message> canvas::Program<Message> for Theme {
fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(bounds.size(), |frame| {
self.draw(frame);
Expand Down
12 changes: 12 additions & 0 deletions examples/pure/component/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "pure_component"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false

[dependencies]
iced = { path = "../../..", features = ["debug", "pure"] }
iced_native = { path = "../../../native" }
iced_lazy = { path = "../../../lazy", features = ["pure"] }
iced_pure = { path = "../../../pure" }
166 changes: 166 additions & 0 deletions examples/pure/component/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use iced::pure::container;
use iced::pure::{Element, Sandbox};
use iced::{Length, Settings};

use numeric_input::numeric_input;

pub fn main() -> iced::Result {
Component::run(Settings::default())
}

#[derive(Default)]
struct Component {
value: Option<u32>,
}

#[derive(Debug, Clone, Copy)]
enum Message {
NumericInputChanged(Option<u32>),
}

impl Sandbox for Component {
type Message = Message;

fn new() -> Self {
Self::default()
}

fn title(&self) -> String {
String::from("Component - Iced")
}

fn update(&mut self, message: Message) {
match message {
Message::NumericInputChanged(value) => {
self.value = value;
}
}
}

fn view(&self) -> Element<Message> {
container(numeric_input(self.value, Message::NumericInputChanged))
.padding(20)
.height(Length::Fill)
.center_y()
.into()
}
}

mod numeric_input {
use iced::pure::{button, row, text, text_input};
use iced_lazy::pure::{self, Component};
use iced_native::alignment::{self, Alignment};
use iced_native::text;
use iced_native::Length;
use iced_pure::Element;

pub struct NumericInput<Message> {
value: Option<u32>,
on_change: Box<dyn Fn(Option<u32>) -> Message>,
}

pub fn numeric_input<Message>(
value: Option<u32>,
on_change: impl Fn(Option<u32>) -> Message + 'static,
) -> NumericInput<Message> {
NumericInput::new(value, on_change)
}

#[derive(Debug, Clone)]
pub enum Event {
InputChanged(String),
IncrementPressed,
DecrementPressed,
}

impl<Message> NumericInput<Message> {
pub fn new(
value: Option<u32>,
on_change: impl Fn(Option<u32>) -> Message + 'static,
) -> Self {
Self {
value,
on_change: Box::new(on_change),
}
}
}

impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
where
Renderer: text::Renderer + 'static,
{
type State = ();
type Event = Event;

fn update(
&mut self,
_state: &mut Self::State,
event: Event,
) -> Option<Message> {
match event {
Event::IncrementPressed => Some((self.on_change)(Some(
self.value.unwrap_or_default().saturating_add(1),
))),
Event::DecrementPressed => Some((self.on_change)(Some(
self.value.unwrap_or_default().saturating_sub(1),
))),
Event::InputChanged(value) => {
if value.is_empty() {
Some((self.on_change)(None))
} else {
value
.parse()
.ok()
.map(Some)
.map(self.on_change.as_ref())
}
}
}
}

fn view(&self, _state: &Self::State) -> Element<Event, Renderer> {
let button = |label, on_press| {
button(
text(label)
.width(Length::Fill)
.height(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center),
)
.width(Length::Units(50))
.on_press(on_press)
};

row()
.push(button("-", Event::DecrementPressed))
.push(
text_input(
"Type a number",
self.value
.as_ref()
.map(u32::to_string)
.as_ref()
.map(String::as_str)
.unwrap_or(""),
Event::InputChanged,
)
.padding(10),
)
.push(button("+", Event::IncrementPressed))
.align_items(Alignment::Fill)
.spacing(10)
.into()
}
}

impl<'a, Message, Renderer> From<NumericInput<Message>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'static + text::Renderer,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
pure::component(numeric_input)
}
}
}
9 changes: 9 additions & 0 deletions examples/pure/counter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "pure_counter"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false

[dependencies]
iced = { path = "../../..", features = ["pure"] }
49 changes: 49 additions & 0 deletions examples/pure/counter/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use iced::pure::{button, column, text, Element, Sandbox};
use iced::{Alignment, Settings};

pub fn main() -> iced::Result {
Counter::run(Settings::default())
}

struct Counter {
value: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
IncrementPressed,
DecrementPressed,
}

impl Sandbox for Counter {
type Message = Message;

fn new() -> Self {
Self { value: 0 }
}

fn title(&self) -> String {
String::from("Counter - Iced")
}

fn update(&mut self, message: Message) {
match message {
Message::IncrementPressed => {
self.value += 1;
}
Message::DecrementPressed => {
self.value -= 1;
}
}
}

fn view(&self) -> Element<Message> {
column()
.padding(20)
.align_items(Alignment::Center)
.push(button("Increment").on_press(Message::IncrementPressed))
.push(text(self.value.to_string()).size(50))
.push(button("Decrement").on_press(Message::DecrementPressed))
.into()
}
}
13 changes: 13 additions & 0 deletions examples/pure/game_of_life/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "pure_game_of_life"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false

[dependencies]
iced = { path = "../../..", features = ["pure", "canvas", "tokio", "debug"] }
tokio = { version = "1.0", features = ["sync"] }
itertools = "0.9"
rustc-hash = "1.1"
env_logger = "0.9"
22 changes: 22 additions & 0 deletions examples/pure/game_of_life/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Game of Life

An interactive version of the [Game of Life], invented by [John Horton Conway].

It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support.

The __[`main`]__ file contains the relevant code of the example.

<div align="center">
<a href="https://gfycat.com/WhichPaltryChick">
<img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
</a>
</div>

You can run it with `cargo run`:
```
cargo run --package game_of_life
```

[`main`]: src/main.rs
[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway
Loading

0 comments on commit 0eef527

Please sign in to comment.