Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateless widgets #1284

Merged
merged 57 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8f0839e
Draft `iced_virtual` subcrate
hecrj Feb 9, 2022
5225e0e
Draft virtual `Button`, `Column`, and `Text`
hecrj Feb 10, 2022
e03de01
Implement `Into<Element>` for `&'static str` in `iced_virtual`
hecrj Feb 10, 2022
8971883
Rename `iced_virtual` to `iced_pure`
hecrj Feb 11, 2022
66d69b5
Expose `iced_pure` through a `pure` feature in `iced`
hecrj Feb 11, 2022
43a7ad7
Expose function helpers to build widgets in `pure::widget`
hecrj Feb 11, 2022
01c5004
Allow pure widgets to borrow from `Application` data :tada:
hecrj Feb 11, 2022
ecb3df8
Expose reusable `Button` logic
hecrj Feb 11, 2022
dd3e74e
Complete `Button` in `iced_pure`
hecrj Feb 11, 2022
af12226
Implement `Row` in `iced_pure`
hecrj Feb 11, 2022
8b27083
Use `TypeId` of `()` for `Column` and `Row` tags in `iced_pure`
hecrj Feb 11, 2022
182fb94
Implement `Container` widget in `iced_pure`
hecrj Feb 12, 2022
dee3dba
Reuse `Text` widget from `iced_native` in `iced_pure`
hecrj Feb 12, 2022
178914e
Implement `Checkbox` in `iced_pure`
hecrj Feb 12, 2022
b2670e8
Implement `Scrollable` in `iced_pure`
hecrj Feb 12, 2022
e310849
Implement `TextInput` in `iced_pure`
hecrj Feb 12, 2022
bd22cc0
Implement pure version of `todos` example :tada:
hecrj Feb 12, 2022
4c61601
Implement missing `on_event` and `mouse_interaction` for `Checkbox` i…
hecrj Feb 12, 2022
09c96a6
Add `max_width` to `Column` in `iced_pure`
hecrj Feb 12, 2022
45455be
Implement `Image` in `iced_pure`
hecrj Feb 13, 2022
3f1a45c
Implement `Slider` in `iced_pure`
hecrj Feb 13, 2022
0fec0a2
Implement `Toggler` in `iced_pure`
hecrj Feb 13, 2022
9875078
Introduce lifetime to `on_change` handler for `Toggler`
hecrj Feb 13, 2022
e50e639
Expose additional helpers in `iced::pure`
hecrj Feb 13, 2022
53f3820
Implement `Radio` in `iced_pure`
hecrj Feb 13, 2022
6689ede
Implement `Space` in `iced_pure`
hecrj Feb 13, 2022
cff8918
Implement `pure` version of the `tour` example :tada:
hecrj Feb 13, 2022
35e9b75
Introduce `Tag` and `State` opaque types in `iced_pure::widget::tree`
hecrj Feb 16, 2022
019af8d
Add `overlay` support in `iced_pure` and port `PickList` :tada:
hecrj Feb 16, 2022
6e242fe
Add `pick_list` function helper in `iced_pure::widget`
hecrj Feb 16, 2022
0ca0662
Fix `overlay` translation for `Scrollable` in `iced_pure`
hecrj Feb 16, 2022
2737b21
Implement `pure` version of `pick_list` example :tada:
hecrj Feb 16, 2022
da45b6c
Implement `pure::Component` in `iced_lazy`
hecrj Feb 17, 2022
9b23ea6
Implement `pure` version of `component` example
hecrj Feb 17, 2022
820d332
Fix `subscription` for `iced::pure::Application`
hecrj Feb 20, 2022
c35496d
Merge branch 'master' into virtual-widgets
hecrj Mar 7, 2022
fbbb864
Merge branch 'master' into virtual-widgets
hecrj Mar 7, 2022
fa3bd42
Initialize `lazy::pure::Component` view properly
hecrj Mar 7, 2022
9fd66c8
Introduce `rebuild_element` helper in `lazy::pure::Component`
hecrj Mar 7, 2022
b50e208
Implement `pure::Responsive` in `iced_lazy`
hecrj Mar 7, 2022
7d9ab71
Remove superfluous files from `pure` examples
hecrj Mar 8, 2022
12c1a3f
Remove redundant `widget` modules in subcrates
hecrj Mar 9, 2022
c52fd08
Use associated type for `Message` in a `canvas::Program`
hecrj Mar 9, 2022
0cddb3c
Implement `pure` version of `Canvas` widget
hecrj Mar 9, 2022
7d7064a
Implement `pure` version of `game_of_life` example :tada:
hecrj Mar 9, 2022
31d814b
Implement `Widget::tag` for `pure::Canvas`
hecrj Mar 9, 2022
0fbd1d9
Implement `pure` version of `Rule` widget
hecrj Mar 10, 2022
3efb59d
Implement `pure` version of `ProgressBar` widget
hecrj Mar 10, 2022
9f27969
Fix incorrect `layout` in `Widget::overlay` for `pure::Responsive`
hecrj Mar 14, 2022
6dd187f
Implement `pure` version of `PaneGrid` widget
hecrj Mar 10, 2022
cdd906f
Implement `pure` version of `pane_grid` example :tada:
hecrj Mar 14, 2022
d7100fd
Export widget modules in `iced_pure`
hecrj Mar 16, 2022
32fd8da
Reintroduce generic `Message` type for `canvas::Program`
hecrj Mar 18, 2022
497a3ca
Restore `TextInput::draw` helper
hecrj Mar 18, 2022
989c562
Implement `pure` version of `QRCode` widget
hecrj Mar 21, 2022
9157f5b
Use application lifetime in `Into<Element>` implementation for `&str`
hecrj Mar 22, 2022
ef4c79e
Implement `pure` version of `Svg` widget
hecrj Mar 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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