Compare commits

...

52 Commits

Author SHA1 Message Date
Olivier 'reivilibre' 2dc42b4a22 Use less iterator magic to avoid compile error on phone 2021-06-26 20:27:44 +01:00
Olivier 'reivilibre' 343e6332d7 Format code 2021-06-26 20:19:55 +01:00
Olivier 'reivilibre' 3a5f866782 Downgrade OpenGL and GLSL for glow renderer 2021-06-26 20:19:51 +01:00
Olivier 'reivilibre' e4b626b91f Use glow_glyph_compat 2021-06-26 19:47:12 +01:00
Olivier 'reivilibre' 45ecf02504 Add tour_glow for OpenGL platforms 2021-06-26 16:09:25 +01:00
Héctor Ramón Jiménez 1b6cf05f5f
Install `libxkbcommon-dev` for `ubuntu-latest` in CI 2021-06-26 12:19:23 +02:00
Héctor Ramón c6c3594c83
Merge pull request #927 from diegodox/fix-typo
Fix typo in documentation of `canvas::Program`
2021-06-26 11:57:31 +02:00
Héctor Ramón 06d0158efb
Merge pull request #917 from derezzedex/macos-url
Enable receiving URLs on MacOS
2021-06-25 14:47:14 +02:00
Richard 612585109f
Use `winit` and `glutin` forks in `iced-rs` org 2021-06-25 14:15:11 +02:00
Richard 96a462d2f2
Use new enum variant and new winit repo 2021-06-25 14:14:03 +02:00
Richard 9ae22b58d8
Added events for url handling and create example 2021-06-25 14:14:03 +02:00
Héctor Ramón d2c8a3e04b
Merge pull request #919 from Imberflur/winit-0.25
Bump winit to 0.25
2021-06-24 19:30:59 +02:00
Imbris ba51661a2a Bump winit to 0.25 2021-06-23 17:38:08 -04:00
Diego Fujii 80df17ab55 fix-typo 2021-06-24 00:16:08 +09:00
Héctor Ramón b62fcca9b9
Merge pull request #915 from yusdacra/docs/update-links
docs: update all 0.2 github links to 0.3
2021-06-22 12:19:38 +02:00
Héctor Ramón 94ee2566c4
Merge pull request #920 from clarkmoody/feature/pane-grid-title-bar-overlay
Pane Grid Title Bar Overlay
2021-06-22 12:02:07 +02:00
Héctor Ramón bb6e06127e
Merge pull request #925 from PolyMeilex/master
Update `wgpu` to `0.9`
2021-06-22 11:39:18 +02:00
Héctor Ramón Jiménez 15c17a7250
Use `match` statement in `Content::overlay`
... to improve readability a bit.
2021-06-22 11:36:36 +02:00
Poly a53e7559fe Use vertex_attr_array macro 2021-06-22 11:23:11 +02:00
Poly c70f90f320 Update wgpu 0.9 2021-06-22 11:23:10 +02:00
Clark Moody 27b42ca6b6 Allow overlay from pane grid title bar 2021-06-17 14:51:23 -05:00
Yusuf Bera Ertan 83d19689c8
docs: update all 0.2 github links to 0.3 2021-06-14 21:01:37 +03:00
Héctor Ramón e68da229b3
Merge pull request #646 from mtsr/disable-button
Disabled button docs and consistent behavior
2021-06-10 19:12:11 +07:00
Héctor Ramón d1797dda87 Revert changes in `tour` example 2021-06-10 18:58:44 +07:00
Héctor Ramón d46dd67a91 Update disabled example of `Button` in docs 2021-06-10 18:58:40 +07:00
Jonas Matser e66120b9c1 Fix failing doctests 2021-06-10 18:28:38 +07:00
Jonas Matser dbc1181011 Adds doc comment for disabled button
Makes disabled button behavior consistent in web
2021-06-10 18:28:37 +07:00
Héctor Ramón f6ff87bb8f
Merge pull request #818 from thenlevy/check_bounds
Prevent scissor_rect region to be larger than the target texture in wgpu::Backend::flush
2021-06-10 18:24:42 +07:00
Héctor Ramón 56f673d819 Revert "Attempt to fix scissor_rect validation error"
This reverts commit 656dc357f8.
2021-06-09 21:30:48 +07:00
Héctor Ramón 5224cc7f26 Floor `width` and `height` in `Rectangle::floor` 2021-06-09 21:30:20 +07:00
nlevy 656dc357f8 Attempt to fix scissor_rect validation error
Update wgpu/src/backend.rs

Cargo fmt
2021-06-09 21:30:00 +07:00
Héctor Ramón 0e70b11e00
Merge pull request #607 from yusdacra/scrollable_programmatically
Add methods to control `Scrollable` programmatically
2021-06-07 22:00:07 +07:00
Héctor Ramón ce3a5f19b9 Add scrolling progress indicators to `scrollable` example 2021-06-04 20:46:47 +07:00
Héctor Ramón 3051d4ec76 Introduce `on_scroll` event in `Scrollable` 2021-06-04 20:46:27 +07:00
Héctor Ramón 57510c43c8 Add buttons to control scrolling in `scrollable` example 2021-06-04 20:15:06 +07:00
Héctor Ramón 827577c179 Introduce `snap_to` and `unsnap` to `scrollable::State` 2021-06-04 20:09:15 +07:00
Yusuf Bera Ertan f7d6e40bf0 feat(native): Make scrollable programmatically scrollable for some use cases, add snap_to_bottom by default 2021-06-04 19:29:58 +07:00
Héctor Ramón 397a5c06ec
Merge pull request #535 from Kaiden42/toggler
Implement `Toggler` widget for iced_native
2021-06-03 20:55:50 +07:00
Héctor Ramón d3d6f3efb3 Add some horizontal padding to `toggler` section in `tour` example 2021-06-03 20:35:26 +07:00
Héctor Ramón ef5f46bcdd Use intra-doc links in `Toggler` docs 2021-06-03 20:28:36 +07:00
Héctor Ramón a32ce271bd Rename `text_align` to `text_alignment` in `Toggler` 2021-06-03 20:27:32 +07:00
Kaiden42 2a5aa69024 Fix format 2021-06-03 20:21:55 +07:00
Kaiden42 e00fca6372 Add `Toggler` widget to `iced_web` 2021-06-03 20:21:55 +07:00
Kaiden42 c0cfd9d5cf Update documentation of `Toggler` 2021-06-03 20:21:55 +07:00
Kaiden42 be3ee9adf1 Add `Toggler` to tour example 2021-06-03 20:21:55 +07:00
Kaiden42 1ef38cc207 Add `Toggler` to styling example 2021-06-03 20:21:55 +07:00
Kaiden42 7a626f3b7b Change label of `Toggler` to optional 2021-06-03 20:21:55 +07:00
Kaiden42 aa18a6e0d5 Add alignment of `Toggler` label. 2021-06-03 20:21:55 +07:00
Kaiden42 bab71971fb fix format 2021-06-03 20:21:55 +07:00
Kaiden42 88da268724 add missing glow support 2021-06-03 20:21:53 +07:00
Kaiden42 7370dfac6e fix missing semicolon in doc test 2021-06-03 20:21:04 +07:00
Kaiden42 52a185fbab Implement `Toggler` widget for iced_native 2021-06-03 20:21:02 +07:00
57 changed files with 2156 additions and 221 deletions

View File

@ -11,6 +11,11 @@ jobs:
- name: Install cargo-deb
run: cargo install cargo-deb
- uses: actions/checkout@master
- name: Install dependencies
run: |
export DEBIAN_FRONTED=noninteractive
sudo apt-get -qq update
sudo apt-get install -y libxkbcommon-dev
- name: Enable Link Time Optimizations
run: |
echo "[profile.release]" >> Cargo.toml

View File

@ -12,6 +12,12 @@ jobs:
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
- name: Install dependencies
if: matrix.os == 'ubuntu-latest'
run: |
export DEBIAN_FRONTED=noninteractive
sudo apt-get -qq update
sudo apt-get install -y libxkbcommon-dev
- name: Run tests
run: |
cargo test --verbose --all

View File

@ -83,7 +83,9 @@ members = [
"examples/svg",
"examples/todos",
"examples/tour",
"examples/tour_glow",
"examples/tooltip",
"examples/url_handler",
]
[dependencies]

View File

@ -105,8 +105,8 @@ impl Rectangle<f32> {
Rectangle {
x: self.x as u32,
y: self.y as u32,
width: self.width.ceil() as u32,
height: self.height.ceil() as u32,
width: self.width as u32,
height: self.height as u32,
}
}
}

View File

@ -1,8 +1,8 @@
mod style;
use iced::{
button, scrollable, Button, Column, Container, Element, Length, Radio, Row,
Rule, Sandbox, Scrollable, Settings, Space, Text,
button, scrollable, Button, Column, Container, Element, Length,
ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
};
pub fn main() -> iced::Result {
@ -17,6 +17,9 @@ struct ScrollableDemo {
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(style::Theme),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
}
impl Sandbox for ScrollableDemo {
@ -36,6 +39,25 @@ impl Sandbox for ScrollableDemo {
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
Message::ScrollToTop(i) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.scrollable.snap_to(0.0);
variant.latest_offset = 0.0;
}
}
Message::ScrollToBottom(i) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.scrollable.snap_to(1.0);
variant.latest_offset = 1.0;
}
}
Message::Scrolled(i, offset) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.latest_offset = offset;
}
}
}
}
@ -62,15 +84,28 @@ impl Sandbox for ScrollableDemo {
let scrollable_row = Row::with_children(
variants
.iter_mut()
.map(|variant| {
.enumerate()
.map(|(i, variant)| {
let mut scrollable =
Scrollable::new(&mut variant.scrollable)
.padding(10)
.spacing(10)
.width(Length::Fill)
.height(Length::Fill)
.on_scroll(move |offset| {
Message::Scrolled(i, offset)
})
.style(*theme)
.push(Text::new(variant.title));
.push(Text::new(variant.title))
.push(
Button::new(
&mut variant.scroll_to_bottom,
Text::new("Scroll to bottom"),
)
.width(Length::Fill)
.padding(10)
.on_press(Message::ScrollToBottom(i)),
);
if let Some(scrollbar_width) = variant.scrollbar_width {
scrollable = scrollable
@ -110,20 +145,31 @@ impl Sandbox for ScrollableDemo {
.push(Space::with_height(Length::Units(1200)))
.push(Text::new("Middle"))
.push(Space::with_height(Length::Units(1200)))
.push(Text::new("The End."))
.push(
Button::new(
&mut variant.button,
Text::new("I am a button"),
&mut variant.scroll_to_top,
Text::new("Scroll to top"),
)
.width(Length::Fill)
.padding(10),
)
.push(Text::new("The End."));
.padding(10)
.on_press(Message::ScrollToTop(i)),
);
Column::new()
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.push(
Container::new(scrollable)
.width(Length::Fill)
.height(Length::Fill)
.style(*theme)
.style(*theme),
)
.push(ProgressBar::new(
0.0..=1.0,
variant.latest_offset,
))
.into()
})
.collect(),
@ -153,10 +199,12 @@ impl Sandbox for ScrollableDemo {
struct Variant {
title: &'static str,
scrollable: scrollable::State,
button: button::State,
scroll_to_top: button::State,
scroll_to_bottom: button::State,
scrollbar_width: Option<u16>,
scrollbar_margin: Option<u16>,
scroller_width: Option<u16>,
latest_offset: f32,
}
impl Variant {
@ -165,34 +213,42 @@ impl Variant {
Self {
title: "Default Scrollbar",
scrollable: scrollable::State::new(),
button: button::State::new(),
scroll_to_top: button::State::new(),
scroll_to_bottom: button::State::new(),
scrollbar_width: None,
scrollbar_margin: None,
scroller_width: None,
latest_offset: 0.0,
},
Self {
title: "Slimmed & Margin",
scrollable: scrollable::State::new(),
button: button::State::new(),
scroll_to_top: button::State::new(),
scroll_to_bottom: button::State::new(),
scrollbar_width: Some(4),
scrollbar_margin: Some(3),
scroller_width: Some(4),
latest_offset: 0.0,
},
Self {
title: "Wide Scroller",
scrollable: scrollable::State::new(),
button: button::State::new(),
scroll_to_top: button::State::new(),
scroll_to_bottom: button::State::new(),
scrollbar_width: Some(4),
scrollbar_margin: None,
scroller_width: Some(10),
latest_offset: 0.0,
},
Self {
title: "Narrow Scroller",
scrollable: scrollable::State::new(),
button: button::State::new(),
scroll_to_top: button::State::new(),
scroll_to_bottom: button::State::new(),
scrollbar_width: Some(10),
scrollbar_margin: None,
scroller_width: Some(4),
latest_offset: 0.0,
},
]
}

View File

@ -1,7 +1,7 @@
use iced::{
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
Scrollable, Settings, Slider, Space, Text, TextInput,
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@ -17,7 +17,8 @@ struct Styling {
button: button::State,
slider: slider::State,
slider_value: f32,
toggle_value: bool,
checkbox_value: bool,
toggler_value: bool,
}
#[derive(Debug, Clone)]
@ -27,6 +28,7 @@ enum Message {
ButtonPressed,
SliderChanged(f32),
CheckboxToggled(bool),
TogglerToggled(bool),
}
impl Sandbox for Styling {
@ -46,7 +48,8 @@ impl Sandbox for Styling {
Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
Message::CheckboxToggled(value) => self.toggle_value = value,
Message::CheckboxToggled(value) => self.checkbox_value = value,
Message::TogglerToggled(value) => self.toggler_value = value,
}
}
@ -101,11 +104,19 @@ impl Sandbox for Styling {
.push(Text::new("You did it!"));
let checkbox = Checkbox::new(
self.toggle_value,
"Toggle me!",
self.checkbox_value,
"Check me!",
Message::CheckboxToggled,
)
.width(Length::Fill)
.style(self.theme);
let toggler = Toggler::new(
self.toggler_value,
String::from("Toggle me!"),
Message::TogglerToggled,
)
.width(Length::Shrink)
.spacing(10)
.style(self.theme);
let content = Column::new()
@ -124,7 +135,13 @@ impl Sandbox for Styling {
.align_items(Align::Center)
.push(scrollable)
.push(Rule::vertical(38).style(self.theme))
.push(checkbox),
.push(
Column::new()
.width(Length::Shrink)
.spacing(20)
.push(checkbox)
.push(toggler),
),
);
Container::new(content)
@ -140,7 +157,7 @@ impl Sandbox for Styling {
mod style {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
slider, text_input,
slider, text_input, toggler,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -231,6 +248,15 @@ mod style {
}
}
impl From<Theme> for Box<dyn toggler::StyleSheet> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
Theme::Dark => dark::Toggler.into(),
}
}
}
impl From<Theme> for Box<dyn rule::StyleSheet> {
fn from(theme: Theme) -> Self {
match theme {
@ -269,7 +295,7 @@ mod style {
mod dark {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
slider, text_input, Color,
slider, text_input, toggler, Color,
};
const SURFACE: Color = Color::from_rgb(
@ -520,6 +546,35 @@ mod style {
}
}
pub struct Toggler;
impl toggler::StyleSheet for Toggler {
fn active(&self, is_active: bool) -> toggler::Style {
toggler::Style {
background: if is_active { ACTIVE } else { SURFACE },
background_border: None,
foreground: if is_active { Color::WHITE } else { ACTIVE },
foreground_border: None,
}
}
fn hovered(&self, is_active: bool) -> toggler::Style {
toggler::Style {
background: if is_active { ACTIVE } else { SURFACE },
background_border: None,
foreground: if is_active {
Color {
a: 0.5,
..Color::WHITE
}
} else {
Color { a: 0.5, ..ACTIVE }
},
foreground_border: None,
}
}
}
pub struct Rule;
impl rule::StyleSheet for Rule {

View File

@ -1,7 +1,7 @@
use iced::{
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@ -135,6 +135,9 @@ impl Steps {
color: Color::BLACK,
},
Step::Radio { selection: None },
Step::Toggler {
can_continue: false,
},
Step::Image {
width: 300,
slider: slider::State::new(),
@ -206,6 +209,9 @@ enum Step {
Radio {
selection: Option<Language>,
},
Toggler {
can_continue: bool,
},
Image {
width: u16,
slider: slider::State,
@ -232,6 +238,7 @@ pub enum StepMessage {
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
TogglerChanged(bool),
}
impl<'a> Step {
@ -287,6 +294,11 @@ impl<'a> Step {
*is_secure = toggle;
}
}
StepMessage::TogglerChanged(value) => {
if let Step::Toggler { can_continue, .. } = self {
*can_continue = value;
}
}
};
}
@ -294,6 +306,7 @@ impl<'a> Step {
match self {
Step::Welcome => "Welcome",
Step::Radio { .. } => "Radio button",
Step::Toggler { .. } => "Toggler",
Step::Slider { .. } => "Slider",
Step::Text { .. } => "Text",
Step::Image { .. } => "Image",
@ -309,6 +322,7 @@ impl<'a> Step {
match self {
Step::Welcome => true,
Step::Radio { selection } => *selection == Some(Language::Rust),
Step::Toggler { can_continue } => *can_continue,
Step::Slider { .. } => true,
Step::Text { .. } => true,
Step::Image { .. } => true,
@ -324,6 +338,7 @@ impl<'a> Step {
match self {
Step::Welcome => Self::welcome(),
Step::Radio { selection } => Self::radio(*selection),
Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { state, value } => Self::slider(state, *value),
Step::Text {
size_slider,
@ -545,6 +560,21 @@ impl<'a> Step {
))
}
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
Self::container("Toggler")
.push(Text::new(
"A toggler is mostly used to enable or disable something.",
))
.push(
Container::new(Toggler::new(
can_continue,
String::from("Toggle me to continue..."),
StepMessage::TogglerChanged,
))
.padding([0, 40]),
)
}
fn image(
width: u16,
slider: &'a mut slider::State,

View File

@ -0,0 +1,10 @@
[package]
name = "tour_glow"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
iced = { path = "../..", features = ["glow", "debug"] }
env_logger = "0.8"

View File

@ -0,0 +1,28 @@
## Tour
A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced.
The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.
<div align="center">
<a href="https://gfycat.com/politeadorableiberianmole">
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
</a>
</div>
[`main`]: src/main.rs
[`iced_winit`]: ../../winit
[`iced_native`]: ../../native
[`iced_wgpu`]: ../../wgpu
[`iced_web`]: ../../web
[`winit`]: https://github.com/rust-windowing/winit
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
You can run the native version with `cargo run`:
```
cargo run --package tour
```
The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
[the usage instructions of `iced_web`]: ../../web#usage

View File

@ -0,0 +1,809 @@
use iced::{
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
Container, Element, HorizontalAlignment, Length, Radio, Row, Sandbox,
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
env_logger::init();
Tour::run(Settings::default())
}
pub struct Tour {
steps: Steps,
scroll: scrollable::State,
back_button: button::State,
next_button: button::State,
debug: bool,
}
impl Sandbox for Tour {
type Message = Message;
fn new() -> Tour {
Tour {
steps: Steps::new(),
scroll: scrollable::State::new(),
back_button: button::State::new(),
next_button: button::State::new(),
debug: false,
}
}
fn title(&self) -> String {
format!("{} - Iced", self.steps.title())
}
fn update(&mut self, event: Message) {
match event {
Message::BackPressed => {
self.steps.go_back();
}
Message::NextPressed => {
self.steps.advance();
}
Message::StepMessage(step_msg) => {
self.steps.update(step_msg, &mut self.debug);
}
}
}
fn view(&mut self) -> Element<Message> {
let Tour {
steps,
scroll,
back_button,
next_button,
..
} = self;
let mut controls = Row::new();
if steps.has_previous() {
controls = controls.push(
button(back_button, "Back")
.on_press(Message::BackPressed)
.style(style::Button::Secondary),
);
}
controls = controls.push(Space::with_width(Length::Fill));
if steps.can_continue() {
controls = controls.push(
button(next_button, "Next")
.on_press(Message::NextPressed)
.style(style::Button::Primary),
);
}
let content: Element<_> = Column::new()
.max_width(540)
.spacing(20)
.padding(20)
.push(steps.view(self.debug).map(Message::StepMessage))
.push(controls)
.into();
let content = if self.debug {
content.explain(Color::BLACK)
} else {
content
};
let scrollable = Scrollable::new(scroll)
.push(Container::new(content).width(Length::Fill).center_x());
Container::new(scrollable)
.height(Length::Fill)
.center_y()
.into()
}
}
#[derive(Debug, Clone)]
pub enum Message {
BackPressed,
NextPressed,
StepMessage(StepMessage),
}
struct Steps {
steps: Vec<Step>,
current: usize,
}
impl Steps {
fn new() -> Steps {
Steps {
steps: vec![
Step::Welcome,
Step::Slider {
state: slider::State::new(),
value: 50,
},
Step::RowsAndColumns {
layout: Layout::Row,
spacing_slider: slider::State::new(),
spacing: 20,
},
Step::Text {
size_slider: slider::State::new(),
size: 30,
color_sliders: [slider::State::new(); 3],
color: Color::BLACK,
},
Step::Radio { selection: None },
Step::Toggler {
can_continue: false,
},
Step::Image {
width: 300,
slider: slider::State::new(),
},
Step::Scrollable,
Step::TextInput {
value: String::new(),
is_secure: false,
state: text_input::State::new(),
},
Step::Debugger,
Step::End,
],
current: 0,
}
}
fn update(&mut self, msg: StepMessage, debug: &mut bool) {
self.steps[self.current].update(msg, debug);
}
fn view(&mut self, debug: bool) -> Element<StepMessage> {
self.steps[self.current].view(debug)
}
fn advance(&mut self) {
if self.can_continue() {
self.current += 1;
}
}
fn go_back(&mut self) {
if self.has_previous() {
self.current -= 1;
}
}
fn has_previous(&self) -> bool {
self.current > 0
}
fn can_continue(&self) -> bool {
self.current + 1 < self.steps.len()
&& self.steps[self.current].can_continue()
}
fn title(&self) -> &str {
self.steps[self.current].title()
}
}
enum Step {
Welcome,
Slider {
state: slider::State,
value: u8,
},
RowsAndColumns {
layout: Layout,
spacing_slider: slider::State,
spacing: u16,
},
Text {
size_slider: slider::State,
size: u16,
color_sliders: [slider::State; 3],
color: Color,
},
Radio {
selection: Option<Language>,
},
Toggler {
can_continue: bool,
},
Image {
width: u16,
slider: slider::State,
},
Scrollable,
TextInput {
value: String,
is_secure: bool,
state: text_input::State,
},
Debugger,
End,
}
#[derive(Debug, Clone)]
pub enum StepMessage {
SliderChanged(u8),
LayoutChanged(Layout),
SpacingChanged(u16),
TextSizeChanged(u16),
TextColorChanged(Color),
LanguageSelected(Language),
ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
TogglerChanged(bool),
}
impl<'a> Step {
fn update(&mut self, msg: StepMessage, debug: &mut bool) {
match msg {
StepMessage::DebugToggled(value) => {
if let Step::Debugger = self {
*debug = value;
}
}
StepMessage::LanguageSelected(language) => {
if let Step::Radio { selection } = self {
*selection = Some(language);
}
}
StepMessage::SliderChanged(new_value) => {
if let Step::Slider { value, .. } = self {
*value = new_value;
}
}
StepMessage::TextSizeChanged(new_size) => {
if let Step::Text { size, .. } = self {
*size = new_size;
}
}
StepMessage::TextColorChanged(new_color) => {
if let Step::Text { color, .. } = self {
*color = new_color;
}
}
StepMessage::LayoutChanged(new_layout) => {
if let Step::RowsAndColumns { layout, .. } = self {
*layout = new_layout;
}
}
StepMessage::SpacingChanged(new_spacing) => {
if let Step::RowsAndColumns { spacing, .. } = self {
*spacing = new_spacing;
}
}
StepMessage::ImageWidthChanged(new_width) => {
if let Step::Image { width, .. } = self {
*width = new_width;
}
}
StepMessage::InputChanged(new_value) => {
if let Step::TextInput { value, .. } = self {
*value = new_value;
}
}
StepMessage::ToggleSecureInput(toggle) => {
if let Step::TextInput { is_secure, .. } = self {
*is_secure = toggle;
}
}
StepMessage::TogglerChanged(value) => {
if let Step::Toggler { can_continue, .. } = self {
*can_continue = value;
}
}
};
}
fn title(&self) -> &str {
match self {
Step::Welcome => "Welcome",
Step::Radio { .. } => "Radio button",
Step::Toggler { .. } => "Toggler",
Step::Slider { .. } => "Slider",
Step::Text { .. } => "Text",
Step::Image { .. } => "Image",
Step::RowsAndColumns { .. } => "Rows and columns",
Step::Scrollable => "Scrollable",
Step::TextInput { .. } => "Text input",
Step::Debugger => "Debugger",
Step::End => "End",
}
}
fn can_continue(&self) -> bool {
match self {
Step::Welcome => true,
Step::Radio { selection } => *selection == Some(Language::Rust),
Step::Toggler { can_continue } => *can_continue,
Step::Slider { .. } => true,
Step::Text { .. } => true,
Step::Image { .. } => true,
Step::RowsAndColumns { .. } => true,
Step::Scrollable => true,
Step::TextInput { value, .. } => !value.is_empty(),
Step::Debugger => true,
Step::End => false,
}
}
fn view(&mut self, debug: bool) -> Element<StepMessage> {
match self {
Step::Welcome => Self::welcome(),
Step::Radio { selection } => Self::radio(*selection),
Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { state, value } => Self::slider(state, *value),
Step::Text {
size_slider,
size,
color_sliders,
color,
} => Self::text(size_slider, *size, color_sliders, *color),
Step::Image { width, slider } => Self::image(*width, slider),
Step::RowsAndColumns {
layout,
spacing_slider,
spacing,
} => Self::rows_and_columns(*layout, spacing_slider, *spacing),
Step::Scrollable => Self::scrollable(),
Step::TextInput {
value,
is_secure,
state,
} => Self::text_input(value, *is_secure, state),
Step::Debugger => Self::debugger(debug),
Step::End => Self::end(),
}
.into()
}
fn container(title: &str) -> Column<'a, StepMessage> {
Column::new().spacing(20).push(Text::new(title).size(50))
}
fn welcome() -> Column<'a, StepMessage> {
Self::container("Welcome!")
.push(Text::new(
"This is a simple tour meant to showcase a bunch of widgets \
that can be easily implemented on top of Iced.",
))
.push(Text::new(
"Iced is a cross-platform GUI library for Rust focused on \
simplicity and type-safety. It is heavily inspired by Elm.",
))
.push(Text::new(
"It was originally born as part of Coffee, an opinionated \
2D game engine for Rust.",
))
.push(Text::new(
"On native platforms, Iced provides by default a renderer \
built on top of wgpu, a graphics library supporting Vulkan, \
Metal, DX11, and DX12.",
))
.push(Text::new(
"Additionally, this tour can also run on WebAssembly thanks \
to dodrio, an experimental VDOM library for Rust.",
))
.push(Text::new(
"You will need to interact with the UI in order to reach the \
end!",
))
}
fn slider(
state: &'a mut slider::State,
value: u8,
) -> Column<'a, StepMessage> {
Self::container("Slider")
.push(Text::new(
"A slider allows you to smoothly select a value from a range \
of values.",
))
.push(Text::new(
"The following slider lets you choose an integer from \
0 to 100:",
))
.push(Slider::new(
state,
0..=100,
value,
StepMessage::SliderChanged,
))
.push(
Text::new(&value.to_string())
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center),
)
}
fn rows_and_columns(
layout: Layout,
spacing_slider: &'a mut slider::State,
spacing: u16,
) -> Column<'a, StepMessage> {
let row_radio = Radio::new(
Layout::Row,
"Row",
Some(layout),
StepMessage::LayoutChanged,
);
let column_radio = Radio::new(
Layout::Column,
"Column",
Some(layout),
StepMessage::LayoutChanged,
);
let layout_section: Element<_> = match layout {
Layout::Row => Row::new()
.spacing(spacing)
.push(row_radio)
.push(column_radio)
.into(),
Layout::Column => Column::new()
.spacing(spacing)
.push(row_radio)
.push(column_radio)
.into(),
};
let spacing_section = Column::new()
.spacing(10)
.push(Slider::new(
spacing_slider,
0..=80,
spacing,
StepMessage::SpacingChanged,
))
.push(
Text::new(&format!("{} px", spacing))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center),
);
Self::container("Rows and columns")
.spacing(spacing)
.push(Text::new(
"Iced uses a layout model based on flexbox to position UI \
elements.",
))
.push(Text::new(
"Rows and columns can be used to distribute content \
horizontally or vertically, respectively.",
))
.push(layout_section)
.push(Text::new(
"You can also easily change the spacing between elements:",
))
.push(spacing_section)
}
fn text(
size_slider: &'a mut slider::State,
size: u16,
color_sliders: &'a mut [slider::State; 3],
color: Color,
) -> Column<'a, StepMessage> {
let size_section = Column::new()
.padding(20)
.spacing(20)
.push(Text::new("You can change its size:"))
.push(
Text::new(&format!("This text is {} pixels", size)).size(size),
)
.push(Slider::new(
size_slider,
10..=70,
size,
StepMessage::TextSizeChanged,
));
let [red, green, blue] = color_sliders;
let color_sliders = Row::new()
.spacing(10)
.push(color_slider(red, color.r, move |r| Color { r, ..color }))
.push(color_slider(green, color.g, move |g| Color { g, ..color }))
.push(color_slider(blue, color.b, move |b| Color { b, ..color }));
let color_section = Column::new()
.padding(20)
.spacing(20)
.push(Text::new("And its color:"))
.push(Text::new(&format!("{:?}", color)).color(color))
.push(color_sliders);
Self::container("Text")
.push(Text::new(
"Text is probably the most essential widget for your UI. \
It will try to adapt to the dimensions of its container.",
))
.push(size_section)
.push(color_section)
}
fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
let question = Column::new()
.padding(20)
.spacing(10)
.push(Text::new("Iced is written in...").size(24))
.push(Language::all().iter().cloned().fold(
Column::new().padding(10).spacing(20),
|choices, language| {
choices.push(Radio::new(
language,
language,
selection,
StepMessage::LanguageSelected,
))
},
));
Self::container("Radio button")
.push(Text::new(
"A radio button is normally used to represent a choice... \
Surprise test!",
))
.push(question)
.push(Text::new(
"Iced works very well with iterators! The list above is \
basically created by folding a column over the different \
choices, creating a radio button for each one of them!",
))
}
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
Self::container("Toggler")
.push(Text::new(
"A toggler is mostly used to enable or disable something.",
))
.push(
Container::new(Toggler::new(
can_continue,
String::from("Toggle me to continue..."),
StepMessage::TogglerChanged,
))
.padding([0, 40]),
)
}
fn image(
width: u16,
slider: &'a mut slider::State,
) -> Column<'a, StepMessage> {
Self::container("Image")
.push(Text::new("An image that tries to keep its aspect ratio."))
.push(ferris(width))
.push(Slider::new(
slider,
100..=500,
width,
StepMessage::ImageWidthChanged,
))
.push(
Text::new(&format!("Width: {} px", width.to_string()))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center),
)
}
fn scrollable() -> Column<'a, StepMessage> {
Self::container("Scrollable")
.push(Text::new(
"Iced supports scrollable content. Try it out! Find the \
button further below.",
))
.push(
Text::new(
"Tip: You can use the scrollbar to scroll down faster!",
)
.size(16),
)
.push(Column::new().height(Length::Units(4096)))
.push(
Text::new("You are halfway there!")
.width(Length::Fill)
.size(30)
.horizontal_alignment(HorizontalAlignment::Center),
)
.push(Column::new().height(Length::Units(4096)))
.push(ferris(300))
.push(
Text::new("You made it!")
.width(Length::Fill)
.size(50)
.horizontal_alignment(HorizontalAlignment::Center),
)
}
fn text_input(
value: &str,
is_secure: bool,
state: &'a mut text_input::State,
) -> Column<'a, StepMessage> {
let text_input = TextInput::new(
state,
"Type something to continue...",
value,
StepMessage::InputChanged,
)
.padding(10)
.size(30);
Self::container("Text input")
.push(Text::new(
"Use a text input to ask for different kinds of information.",
))
.push(if is_secure {
text_input.password()
} else {
text_input
})
.push(Checkbox::new(
is_secure,
"Enable password mode",
StepMessage::ToggleSecureInput,
))
.push(Text::new(
"A text input produces a message every time it changes. It is \
very easy to keep track of its contents:",
))
.push(
Text::new(if value.is_empty() {
"You have not typed anything yet..."
} else {
value
})
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center),
)
}
fn debugger(debug: bool) -> Column<'a, StepMessage> {
Self::container("Debugger")
.push(Text::new(
"You can ask Iced to visually explain the layouting of the \
different elements comprising your UI!",
))
.push(Text::new(
"Give it a shot! Check the following checkbox to be able to \
see element boundaries.",
))
.push(if cfg!(target_arch = "wasm32") {
Element::new(
Text::new("Not available on web yet!")
.color([0.7, 0.7, 0.7])
.horizontal_alignment(HorizontalAlignment::Center),
)
} else {
Element::new(Checkbox::new(
debug,
"Explain layout",
StepMessage::DebugToggled,
))
})
.push(Text::new("Feel free to go back and take a look."))
}
fn end() -> Column<'a, StepMessage> {
Self::container("You reached the end!")
.push(Text::new(
"This tour will be updated as more features are added.",
))
.push(Text::new("Make sure to keep an eye on it!"))
}
}
fn ferris<'a>(_width: u16) -> Container<'a, StepMessage> {
Container::new(
// This should go away once we unify resource loading on native
// platforms
Text::new("sorry ferris, you can't come out in glow."),
)
.width(Length::Fill)
.center_x()
}
fn button<'a, Message: Clone>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
Button::new(
state,
Text::new(label).horizontal_alignment(HorizontalAlignment::Center),
)
.padding(12)
.min_width(100)
}
fn color_slider(
state: &mut slider::State,
component: f32,
update: impl Fn(f32) -> Color + 'static,
) -> Slider<f64, StepMessage> {
Slider::new(state, 0.0..=1.0, f64::from(component), move |c| {
StepMessage::TextColorChanged(update(c as f32))
})
.step(0.01)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
Elm,
Ruby,
Haskell,
C,
Other,
}
impl Language {
fn all() -> [Language; 6] {
[
Language::C,
Language::Elm,
Language::Ruby,
Language::Haskell,
Language::Rust,
Language::Other,
]
}
}
impl From<Language> for String {
fn from(language: Language) -> String {
String::from(match language {
Language::Rust => "Rust",
Language::Elm => "Elm",
Language::Ruby => "Ruby",
Language::Haskell => "Haskell",
Language::C => "C",
Language::Other => "Other",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layout {
Row,
Column,
}
mod style {
use iced::{button, Background, Color, Vector};
pub enum Button {
Primary,
Secondary,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
})),
border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
}
}
fn hovered(&self) -> button::Style {
button::Style {
text_color: Color::WHITE,
shadow_offset: Vector::new(1.0, 2.0),
..self.active()
}
}
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "url_handler"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }

View File

@ -0,0 +1,73 @@
use iced::{
executor, Application, Clipboard, Command, Container, Element, Length,
Settings, Subscription, Text,
};
use iced_native::{
event::{MacOS, PlatformSpecific},
Event,
};
pub fn main() -> iced::Result {
App::run(Settings::default())
}
#[derive(Debug, Default)]
struct App {
url: Option<String>,
}
#[derive(Debug, Clone)]
enum Message {
EventOccurred(iced_native::Event),
}
impl Application for App {
type Executor = executor::Default;
type Message = Message;
type Flags = ();
fn new(_flags: ()) -> (App, Command<Message>) {
(App::default(), Command::none())
}
fn title(&self) -> String {
String::from("Url - Iced")
}
fn update(
&mut self,
message: Message,
_clipboard: &mut Clipboard,
) -> Command<Message> {
match message {
Message::EventOccurred(event) => {
if let Event::PlatformSpecific(PlatformSpecific::MacOS(
MacOS::ReceivedUrl(url),
)) = event
{
self.url = Some(url);
}
}
};
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred)
}
fn view(&mut self) -> Element<Message> {
let content = match &self.url {
Some(url) => Text::new(format!("{}", url)),
None => Text::new("No URL received yet!"),
};
Container::new(content.size(48))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}

View File

@ -125,9 +125,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
/// to listen to time.
///
/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch
/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].

View File

@ -17,7 +17,7 @@ svg = []
[dependencies]
glow = "0.6"
glow_glyph = "0.4"
glow_glyph = { version = "0.4", git = "https://bics.ga/reivilibre/glow_glyph_compat.git", commit = "13ce059bdfe1b3745fa222d73fb2faaf0ec0311d" }
glyph_brush = "0.7"
euclid = "0.22"
bytemuck = "1.4"

View File

@ -6,6 +6,13 @@ use iced_native::Rectangle;
const MAX_INSTANCES: usize = 100_000;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct QuadWithQPos(layer::Quad, [f32; 2]);
unsafe impl bytemuck::Zeroable for QuadWithQPos {}
unsafe impl bytemuck::Pod for QuadWithQPos {}
#[derive(Debug)]
pub struct Pipeline {
program: <glow::Context as HasContext>::Program,
@ -133,23 +140,34 @@ impl Pipeline {
let mut i = 0;
let total = instances.len();
let mut tagged_instances: Vec<QuadWithQPos> = Vec::new();
let pos_map = [[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]];
while i < total {
let end = (i + MAX_INSTANCES).min(total);
let amount = end - i;
unsafe {
tagged_instances.clear();
tagged_instances.reserve((end - i) * 4);
for quad in instances[i..end].iter() {
tagged_instances.push(QuadWithQPos(*quad, pos_map[0]));
tagged_instances.push(QuadWithQPos(*quad, pos_map[1]));
tagged_instances.push(QuadWithQPos(*quad, pos_map[2]));
tagged_instances.push(QuadWithQPos(*quad, pos_map[3]));
}
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(&instances[i..end]),
bytemuck::cast_slice(&tagged_instances),
);
gl.draw_arrays_instanced(
glow::TRIANGLE_STRIP,
0,
4,
amount as i32,
);
for j in 0..amount as i32 {
gl.draw_arrays(glow::TRIANGLE_STRIP, j * 4, 4);
}
}
i += MAX_INSTANCES;
@ -177,23 +195,20 @@ unsafe fn create_instance_buffer(
gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
gl.buffer_data_size(
glow::ARRAY_BUFFER,
(size * std::mem::size_of::<layer::Quad>()) as i32,
(size * std::mem::size_of::<QuadWithQPos>()) as i32,
glow::DYNAMIC_DRAW,
);
let stride = std::mem::size_of::<layer::Quad>() as i32;
let stride = std::mem::size_of::<QuadWithQPos>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
gl.vertex_attrib_divisor(0, 1);
gl.enable_vertex_attrib_array(1);
gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2);
gl.vertex_attrib_divisor(1, 1);
gl.enable_vertex_attrib_array(2);
gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2));
gl.vertex_attrib_divisor(2, 1);
gl.enable_vertex_attrib_array(3);
gl.vertex_attrib_pointer_f32(
@ -204,7 +219,6 @@ unsafe fn create_instance_buffer(
stride,
4 * (2 + 2 + 4),
);
gl.vertex_attrib_divisor(3, 1);
gl.enable_vertex_attrib_array(4);
gl.vertex_attrib_pointer_f32(
@ -215,7 +229,6 @@ unsafe fn create_instance_buffer(
stride,
4 * (2 + 2 + 4 + 4),
);
gl.vertex_attrib_divisor(4, 1);
gl.enable_vertex_attrib_array(5);
gl.vertex_attrib_pointer_f32(
@ -226,7 +239,17 @@ unsafe fn create_instance_buffer(
stride,
4 * (2 + 2 + 4 + 4 + 1),
);
gl.vertex_attrib_divisor(5, 1);
// q_Pos
gl.enable_vertex_attrib_array(6);
gl.vertex_attrib_pointer_f32(
6,
2,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 1 + 1),
);
gl.bind_vertex_array(None);
gl.bind_buffer(glow::ARRAY_BUFFER, None);

View File

@ -1,15 +1,13 @@
#version 330
#version 120
uniform float u_ScreenHeight;
in vec4 v_Color;
in vec4 v_BorderColor;
in vec2 v_Pos;
in vec2 v_Scale;
in float v_BorderRadius;
in float v_BorderWidth;
out vec4 o_Color;
varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying float v_BorderRadius;
varying float v_BorderWidth;
float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius)
{
@ -66,5 +64,5 @@ void main() {
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View File

@ -1,31 +1,24 @@
#version 330
#version 120
uniform mat4 u_Transform;
uniform float u_Scale;
layout(location = 0) in vec2 i_Pos;
layout(location = 1) in vec2 i_Scale;
layout(location = 2) in vec4 i_Color;
layout(location = 3) in vec4 i_BorderColor;
layout(location = 4) in float i_BorderRadius;
layout(location = 5) in float i_BorderWidth;
attribute vec2 i_Pos;
attribute vec2 i_Scale;
attribute vec4 i_Color;
attribute vec4 i_BorderColor;
attribute float i_BorderRadius;
attribute float i_BorderWidth;
attribute vec2 q_Pos;
out vec4 v_Color;
out vec4 v_BorderColor;
out vec2 v_Pos;
out vec2 v_Scale;
out float v_BorderRadius;
out float v_BorderWidth;
const vec2 positions[4] = vec2[](
vec2(0.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying float v_BorderRadius;
varying float v_BorderWidth;
void main() {
vec2 q_Pos = positions[gl_VertexID];
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;

View File

@ -1,9 +1,7 @@
#version 330
#version 120
in vec4 v_Color;
out vec4 o_Color;
varying vec4 v_Color;
void main() {
o_Color = v_Color;
gl_FragColor = v_Color;
}

View File

@ -1,11 +1,11 @@
#version 330
#version 120
uniform mat4 u_Transform;
layout(location = 0) in vec2 i_Position;
layout(location = 1) in vec4 i_Color;
attribute vec2 i_Position;
attribute vec4 i_Color;
out vec4 v_Color;
varying vec4 v_Color;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);

View File

@ -20,6 +20,7 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@ -45,6 +46,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[cfg(feature = "canvas")]

View File

@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
pub use iced_graphics::pane_grid::{

View File

@ -0,0 +1,9 @@
//! Show toggle controls using togglers.
use crate::Renderer;
pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;

View File

@ -13,8 +13,10 @@ categories = ["gui"]
[features]
debug = ["iced_winit/debug"]
[dependencies]
glutin = "0.26"
[dependencies.glutin]
version = "0.27"
git = "https://github.com/iced-rs/glutin"
rev = "be6793b5b3defc9452cd1c896cd315ed7442d546"
[dependencies.iced_native]
version = "0.4"

View File

@ -237,6 +237,16 @@ async fn run_instance<A, E, C>(
context.window().request_redraw();
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
)) => {
use iced_native::event;
events.push(iced_native::Event::PlatformSpecific(
event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
url,
)),
));
}
event::Event::UserEvent(message) => {
messages.push(message);
}

View File

@ -20,6 +20,7 @@ pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
mod column;
@ -50,6 +51,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
pub use column::Column;

View File

@ -34,7 +34,7 @@ pub trait Program<Message> {
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
/// [`Frame`]: crate::widget::canvas::Cache
/// [`Frame`]: crate::widget::canvas::Frame
/// [`Cache`]: crate::widget::canvas::Cache
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;

View File

@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults;
use crate::{Backend, Color, Primitive, Renderer};
use iced_native::container;

View File

@ -0,0 +1,99 @@
//! Show toggle controls using togglers.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::toggler;
use iced_native::Rectangle;
pub use iced_style::toggler::{Style, StyleSheet};
/// Makes sure that the border radius of the toggler looks good at every size.
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
/// The space ratio between the background Quad and the Toggler bounds, and
/// between the background Quad and foreground Quad.
const SPACE_RATIO: f32 = 0.05;
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
pub type Toggler<Message, Backend> =
iced_native::Toggler<Message, Renderer<Backend>>;
impl<B> toggler::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = 20;
fn draw(
&mut self,
bounds: Rectangle,
is_active: bool,
is_mouse_over: bool,
label: Option<Self::Output>,
style_sheet: &Self::Style,
) -> Self::Output {
let style = if is_mouse_over {
style_sheet.hovered(is_active)
} else {
style_sheet.active(is_active)
};
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height as f32;
let toggler_background_bounds = Rectangle {
x: bounds.x + space,
y: bounds.y + space,
width: bounds.width - (2.0 * space),
height: bounds.height - (2.0 * space),
};
let toggler_background = Primitive::Quad {
bounds: toggler_background_bounds,
background: style.background.into(),
border_radius,
border_width: 1.0,
border_color: style.background_border.unwrap_or(style.background),
};
let toggler_foreground_bounds = Rectangle {
x: bounds.x
+ if is_active {
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
} else {
2.0 * space
},
y: bounds.y + (2.0 * space),
width: bounds.height - (4.0 * space),
height: bounds.height - (4.0 * space),
};
let toggler_foreground = Primitive::Quad {
bounds: toggler_foreground_bounds,
background: style.foreground.into(),
border_radius,
border_width: 1.0,
border_color: style.foreground_border.unwrap_or(style.foreground),
};
(
Primitive::Group {
primitives: match label {
Some((l, _)) => {
vec![l, toggler_background, toggler_foreground]
}
None => vec![toggler_background, toggler_foreground],
},
},
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}

View File

@ -23,6 +23,27 @@ pub enum Event {
/// A touch event
Touch(touch::Event),
/// A platform specific event
PlatformSpecific(PlatformSpecific),
}
/// A platform specific event
#[derive(Debug, Clone, PartialEq)]
pub enum PlatformSpecific {
/// A MacOS specific event
MacOS(MacOS),
}
/// Describes an event specific to MacOS
#[derive(Debug, Clone, PartialEq)]
pub enum MacOS {
/// Triggered when the app receives an URL from the system
///
/// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
///
/// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
ReceivedUrl(String),
}
/// The status of an [`Event`] after being processed.

View File

@ -1,6 +1,6 @@
use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row,
scrollable, slider, text, text_input, Color, Element, Font,
scrollable, slider, text, text_input, toggler, Color, Element, Font,
HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
VerticalAlignment,
};
@ -288,3 +288,19 @@ impl pane_grid::Renderer for Null {
) {
}
}
impl toggler::Renderer for Null {
type Style = ();
const DEFAULT_SIZE: u16 = 20;
fn draw(
&mut self,
_bounds: Rectangle,
_is_checked: bool,
_is_mouse_over: bool,
_label: Option<Self::Output>,
_style: &Self::Style,
) {
}
}

View File

@ -16,7 +16,7 @@ use std::hash::Hasher;
/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
/// an existing graphical application.
///
/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration
/// [`integration` example]: https://github.com/hecrj/iced/tree/0.3/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,

View File

@ -36,6 +36,7 @@ pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@ -73,6 +74,8 @@ pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
use crate::event::{self, Event};
@ -96,12 +99,12 @@ use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry
/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool
/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget
/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu
/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,

View File

@ -29,6 +29,29 @@ use std::hash::Hash;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
///
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
/// be disabled:
///
/// ```
/// # use iced_native::{button, Text};
/// #
/// # type Button<'a, Message> =
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
/// Button::new(state, Text::new("I'm disabled!"))
/// }
///
/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
/// disabled_button(state).on_press(Message::ButtonPressed)
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
@ -97,6 +120,7 @@ where
}
/// Sets the message that will be produced when the [`Button`] is pressed.
/// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self

View File

@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
mod axis;
mod configuration;
mod content;

View File

@ -193,18 +193,17 @@ where
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
let body_layout = if self.title_bar.is_some() {
if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
let title_bar_layout = children.next()?;
// Overlays only allowed in the pane body, for now at least.
let _title_bar_layout = children.next();
children.next()?
match title_bar.overlay(title_bar_layout) {
Some(overlay) => Some(overlay),
None => self.body.overlay(children.next()?),
}
} else {
layout
};
self.body.overlay(body_layout)
self.body.overlay(layout)
}
}
}

View File

@ -1,6 +1,7 @@
use crate::container;
use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::pane_grid;
use crate::{
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
@ -242,4 +243,11 @@ where
control_status.merge(title_status)
}
pub(crate) fn overlay(
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.content.overlay(layout)
}
}

View File

@ -23,6 +23,7 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scrollbar_margin: u16,
scroller_width: u16,
content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
}
@ -37,6 +38,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scrollbar_margin: 0,
scroller_width: 10,
content: Column::new(),
on_scroll: None,
style: Renderer::Style::default(),
}
}
@ -101,12 +103,22 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the scroller width of the [`Scrollable`] .
/// Silently enforces a minimum value of 1.
///
/// It silently enforces a minimum value of 1.
pub fn scroller_width(mut self, scroller_width: u16) -> Self {
self.scroller_width = scroller_width.max(1);
self
}
/// Sets a function to call when the [`Scrollable`] is scrolled.
///
/// The function takes the new relative offset of the [`Scrollable`]
/// (e.g. `0` means top, while `1` means bottom).
pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
self.on_scroll = Some(Box::new(f));
self
}
/// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@ -121,6 +133,24 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self.content = self.content.push(child);
self
}
fn notify_on_scroll(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
messages: &mut Vec<Message>,
) {
if content_bounds.height <= bounds.height {
return;
}
if let Some(on_scroll) = &self.on_scroll {
messages.push(on_scroll(
self.state.offset.absolute(bounds, content_bounds)
/ (content_bounds.height - bounds.height),
));
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@ -228,6 +258,8 @@ where
}
}
self.notify_on_scroll(bounds, content_bounds, messages);
return event::Status::Captured;
}
Event::Touch(event) => {
@ -251,6 +283,12 @@ where
self.state.scroll_box_touched_at =
Some(cursor_position);
self.notify_on_scroll(
bounds,
content_bounds,
messages,
);
}
}
touch::Event::FingerLifted { .. }
@ -290,6 +328,8 @@ where
content_bounds,
);
self.notify_on_scroll(bounds, content_bounds, messages);
return event::Status::Captured;
}
}
@ -317,6 +357,12 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
self.notify_on_scroll(
bounds,
content_bounds,
messages,
);
return event::Status::Captured;
}
}
@ -418,11 +464,44 @@ where
}
/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy)]
pub struct State {
scroller_grabbed_at: Option<f32>,
scroll_box_touched_at: Option<Point>,
offset: f32,
offset: Offset,
}
impl Default for State {
fn default() -> Self {
Self {
scroller_grabbed_at: None,
scroll_box_touched_at: None,
offset: Offset::Absolute(0.0),
}
}
}
/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
enum Offset {
Absolute(f32),
Relative(f32),
}
impl Offset {
fn absolute(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 {
match self {
Self::Absolute(absolute) => {
let hidden_content =
(content_bounds.height - bounds.height).max(0.0);
absolute.min(hidden_content)
}
Self::Relative(percentage) => {
((content_bounds.height - bounds.height) * percentage).max(0.0)
}
}
}
}
impl State {
@ -443,13 +522,14 @@ impl State {
return;
}
self.offset = (self.offset - delta_y)
self.offset = Offset::Absolute(
(self.offset.absolute(bounds, content_bounds) - delta_y)
.max(0.0)
.min((content_bounds.height - bounds.height) as f32);
.min((content_bounds.height - bounds.height) as f32),
);
}
/// Moves the scroll position to a relative amount, given the bounds of
/// the [`Scrollable`] and its contents.
/// Scrolls the [`Scrollable`] to a relative amount.
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
@ -459,17 +539,29 @@ impl State {
bounds: Rectangle,
content_bounds: Rectangle,
) {
self.snap_to(percentage);
self.unsnap(bounds, content_bounds);
}
/// Snaps the scroll position to a relative amount.
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
pub fn snap_to(&mut self, percentage: f32) {
self.offset = Offset::Relative(percentage.max(0.0).min(1.0));
}
/// Unsnaps the current scroll position, if snapped, given the bounds of the
/// [`Scrollable`] and its contents.
pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
self.offset =
((content_bounds.height - bounds.height) * percentage).max(0.0);
Offset::Absolute(self.offset.absolute(bounds, content_bounds));
}
/// Returns the current scrolling offset of the [`State`], given the bounds
/// of the [`Scrollable`] and its contents.
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
let hidden_content =
(content_bounds.height - bounds.height).max(0.0).round() as u32;
self.offset.min(hidden_content as f32) as u32
self.offset.absolute(bounds, content_bounds) as u32
}
/// Returns whether the scroller is currently grabbed or not.

View File

@ -0,0 +1,277 @@
//! Show toggle controls using togglers.
use std::hash::Hash;
use crate::{
event, layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget,
};
/// A toggler widget
///
/// # Example
///
/// ```
/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
/// }
///
/// let is_active = true;
///
/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
is_active: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: Option<String>,
width: Length,
size: u16,
text_size: Option<u16>,
text_alignment: HorizontalAlignment,
spacing: u16,
font: Renderer::Font,
style: Renderer::Style,
}
impl<Message, Renderer: self::Renderer + text::Renderer>
Toggler<Message, Renderer>
{
/// Creates a new [`Toggler`].
///
/// It expects:
/// * a boolean describing whether the [`Toggler`] is checked or not
/// * An optional label for the [`Toggler`]
/// * a function that will be called when the [`Toggler`] is toggled. It
/// will receive the new state of the [`Toggler`] and must produce a
/// `Message`.
pub fn new<F>(
is_active: bool,
label: impl Into<Option<String>>,
f: F,
) -> Self
where
F: 'static + Fn(bool) -> Message,
{
Toggler {
is_active,
on_toggle: Box::new(f),
label: label.into(),
width: Length::Fill,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
text_size: None,
text_alignment: HorizontalAlignment::Left,
spacing: 0,
font: Renderer::Font::default(),
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Toggler`].
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Toggler`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the text size o the [`Toggler`].
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
}
/// Sets the horizontal alignment of the text of the [`Toggler`]
pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
self.text_alignment = alignment;
self
}
/// Sets the spacing between the [`Toggler`] and the text.
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the [`Font`] of the text of the [`Toggler`]
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the style of the [`Toggler`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
where
Renderer: self::Renderer + text::Renderer + row::Renderer,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let mut row = Row::<(), Renderer>::new()
.width(self.width)
.spacing(self.spacing)
.align_items(Align::Center);
if let Some(label) = &self.label {
row = row.push(
Text::new(label)
.horizontal_alignment(self.text_alignment)
.font(self.font)
.width(self.width)
.size(self.text_size.unwrap_or(renderer.default_size())),
);
}
row = row.push(
Row::new()
.width(Length::Units(2 * self.size))
.height(Length::Units(self.size)),
);
row.layout(renderer, limits)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_active));
event::Status::Captured
} else {
event::Status::Ignored
}
}
_ => event::Status::Ignored,
}
}
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
let label = match &self.label {
Some(label) => {
let label_layout = children.next().unwrap();
Some(text::Renderer::draw(
renderer,
defaults,
label_layout.bounds(),
&label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
None,
self.text_alignment,
VerticalAlignment::Center,
))
}
None => None,
};
let toggler_layout = children.next().unwrap();
let toggler_bounds = toggler_layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
toggler_bounds,
self.is_active,
is_mouse_over,
label,
&self.style,
)
}
fn hash_layout(&self, state: &mut Hasher) {
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);
self.label.hash(state)
}
}
/// The renderer of a [`Toggler`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Toggler`] in your user interface.
///
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Toggler`].
const DEFAULT_SIZE: u16;
/// Draws a [`Toggler`].
///
/// It receives:
/// * the bounds of the [`Toggler`]
/// * whether the [`Toggler`] is activated or not
/// * whether the mouse is over the [`Toggler`] or not
/// * the drawn label of the [`Toggler`]
/// * the style of the [`Toggler`]
fn draw(
&mut self,
bounds: Rectangle,
is_active: bool,
is_mouse_over: bool,
label: Option<Self::Output>,
style: &Self::Style,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
Message: 'a,
{
fn from(
toggler: Toggler<Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(toggler)
}
}

View File

@ -39,15 +39,15 @@ use crate::{
/// to listen to time.
/// - [`todos`], a todos tracker inspired by [TodoMVC].
///
/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.2/examples
/// [`clock`]: https://github.com/hecrj/iced/tree/0.2/examples/clock
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress
/// [`events`]: https://github.com/hecrj/iced/tree/0.2/examples/events
/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.2/examples/game_of_life
/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.2/examples/pokedex
/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.2/examples/solar_system
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch
/// [`todos`]: https://github.com/hecrj/iced/tree/0.2/examples/todos
/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples
/// [`clock`]: https://github.com/hecrj/iced/tree/0.3/examples/clock
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress
/// [`events`]: https://github.com/hecrj/iced/tree/0.3/examples/events
/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.3/examples/game_of_life
/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.3/examples/pokedex
/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.3/examples/solar_system
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch
/// [`todos`]: https://github.com/hecrj/iced/tree/0.3/examples/todos
/// [`Sandbox`]: crate::Sandbox
/// [`Canvas`]: crate::widget::Canvas
/// [PokéAPI]: https://pokeapi.co/

View File

@ -36,19 +36,19 @@ use crate::{
/// - [`tour`], a simple UI tour that can run both on native platforms and the
/// web!
///
/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.2/examples
/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
/// [`counter`]: https://github.com/hecrj/iced/tree/0.2/examples/counter
/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry
/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.2/examples/progress_bar
/// [`styling`]: https://github.com/hecrj/iced/tree/0.2/examples/styling
/// [`svg`]: https://github.com/hecrj/iced/tree/0.2/examples/svg
/// [`tour`]: https://github.com/hecrj/iced/tree/0.2/examples/tour
/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples
/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool
/// [`counter`]: https://github.com/hecrj/iced/tree/0.3/examples/counter
/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget
/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry
/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.3/examples/progress_bar
/// [`styling`]: https://github.com/hecrj/iced/tree/0.3/examples/styling
/// [`svg`]: https://github.com/hecrj/iced/tree/0.3/examples/svg
/// [`tour`]: https://github.com/hecrj/iced/tree/0.3/examples/tour
/// [`Canvas widget`]: crate::widget::Canvas
/// [the overview]: index.html#overview
/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu
/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
/// [`Svg` widget]: crate::widget::Svg
/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
///

View File

@ -17,8 +17,8 @@
mod platform {
pub use crate::renderer::widget::{
button, checkbox, container, pane_grid, pick_list, progress_bar, radio,
rule, scrollable, slider, text_input, tooltip, Column, Row, Space,
Text,
rule, scrollable, slider, text_input, toggler, tooltip, Column, Row,
Space, Text,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
@ -53,7 +53,7 @@ mod platform {
button::Button, checkbox::Checkbox, container::Container, image::Image,
pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar,
radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider,
svg::Svg, text_input::TextInput, tooltip::Tooltip,
svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]

View File

@ -18,3 +18,4 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
pub mod toggler;

57
style/src/toggler.rs Normal file
View File

@ -0,0 +1,57 @@
//! Show toggle controls using togglers.
use iced_core::Color;
/// The appearance of a toggler.
#[derive(Debug)]
pub struct Style {
pub background: Color,
pub background_border: Option<Color>,
pub foreground: Color,
pub foreground_border: Option<Color>,
}
/// A set of rules that dictate the style of a toggler.
pub trait StyleSheet {
fn active(&self, is_active: bool) -> Style;
fn hovered(&self, is_active: bool) -> Style;
}
struct Default;
impl StyleSheet for Default {
fn active(&self, is_active: bool) -> Style {
Style {
background: if is_active {
Color::from_rgb(0.0, 1.0, 0.0)
} else {
Color::from_rgb(0.7, 0.7, 0.7)
},
background_border: None,
foreground: Color::WHITE,
foreground_border: None,
}
}
fn hovered(&self, is_active: bool) -> Style {
Style {
foreground: Color::from_rgb(0.95, 0.95, 0.95),
..self.active(is_active)
}
}
}
impl std::default::Default for Box<dyn StyleSheet> {
fn default() -> Self {
Box::new(Default)
}
}
impl<T> From<T> for Box<dyn StyleSheet>
where
T: 'static + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
}
}

View File

@ -14,6 +14,9 @@ pub enum Rule {
/// Spacing between elements
Spacing(u16),
/// Toggler input for a specific size
Toggler(u16),
}
impl Rule {
@ -23,6 +26,7 @@ impl Rule {
Rule::Column => String::from("c"),
Rule::Row => String::from("r"),
Rule::Spacing(spacing) => format!("s-{}", spacing),
Rule::Toggler(size) => format!("toggler-{}", size),
}
}
@ -55,6 +59,46 @@ impl Rule {
class
)
.into_bump_str(),
Rule::Toggler(size) => bumpalo::format!(
in bump,
".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \
.toggler-{} input {{ display:none; }} \
.toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \
.toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \
.toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \
.toggler-{} input:checked + span {{ background-color: #00FF00; }} \
.toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }}
",
// toggler
size,
// toggler input
size,
// toggler span
size,
size*2,
size,
size,
// toggler span > span
size,
size-2,
size-2,
// toggler: hover + span > span
size,
// toggler input:checked + span
size,
// toggler input:checked + span > span
size,
size,
size,
size
)
.into_bump_str(),
}
}
}

View File

@ -49,7 +49,7 @@
//!
//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack
//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen
//! [`tour` example]: https://github.com/hecrj/iced/tree/0.2/examples/tour
//! [`tour` example]: https://github.com/hecrj/iced/tree/0.3/examples/tour
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]

View File

@ -24,6 +24,7 @@ pub mod radio;
pub mod scrollable;
pub mod slider;
pub mod text_input;
pub mod toggler;
mod column;
mod row;
@ -40,6 +41,8 @@ pub use slider::Slider;
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
pub use checkbox::Checkbox;
pub use column::Column;

View File

@ -20,6 +20,26 @@ use dodrio::bumpalo;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
///
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
/// be disabled:
///
/// ```
/// # use iced_web::{button, Button, Text};
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
/// Button::new(state, Text::new("I'm disabled!"))
/// }
///
/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
/// disabled_button(state).on_press(Message::ButtonPressed)
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message> {
content: Element<'a, Message>,
@ -90,6 +110,7 @@ impl<'a, Message> Button<'a, Message> {
}
/// Sets the message that will be produced when the [`Button`] is pressed.
/// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
@ -153,6 +174,8 @@ where
node = node.on("click", move |_root, _vdom, _event| {
event_bus.publish(on_press.clone());
});
} else {
node = node.attr("disabled", "");
}
node.finish()

171
web/src/widget/toggler.rs Normal file
View File

@ -0,0 +1,171 @@
//! Show toggle controls using togglers.
use crate::{css, Bus, Css, Element, Length, Widget};
pub use iced_style::toggler::{Style, StyleSheet};
use dodrio::bumpalo;
use std::rc::Rc;
/// A toggler that can be toggled.
///
/// # Example
///
/// ```
/// # use iced_web::Toggler;
///
/// pub enum Message {
/// TogglerToggled(bool),
/// }
///
/// let is_active = true;
///
/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled);
/// ```
///
#[allow(missing_debug_implementations)]
pub struct Toggler<Message> {
is_active: bool,
on_toggle: Rc<dyn Fn(bool) -> Message>,
label: Option<String>,
id: Option<String>,
width: Length,
style: Box<dyn StyleSheet>,
}
impl<Message> Toggler<Message> {
/// Creates a new [`Toggler`].
///
/// It expects:
/// * a boolean describing whether the [`Toggler`] is active or not
/// * An optional label for the [`Toggler`]
/// * a function that will be called when the [`Toggler`] is toggled. It
/// will receive the new state of the [`Toggler`] and must produce a
/// `Message`.
///
/// [`Toggler`]: struct.Toggler.html
pub fn new<F>(
is_active: bool,
label: impl Into<Option<String>>,
f: F,
) -> Self
where
F: 'static + Fn(bool) -> Message,
{
Toggler {
is_active,
on_toggle: Rc::new(f),
label: label.into(),
id: None,
width: Length::Shrink,
style: Default::default(),
}
}
/// Sets the width of the [`Toggler`].
///
/// [`Toggler`]: struct.Toggler.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the style of the [`Toggler`].
///
/// [`Toggler`]: struct.Toggler.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Sets the id of the [`Toggler`].
///
/// [`Toggler`]: struct.Toggler.html
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
}
impl<Message> Widget<Message> for Toggler<Message>
where
Message: 'static,
{
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
bus: &Bus<Message>,
style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
use dodrio::builder::*;
use dodrio::bumpalo::collections::String;
let toggler_label = &self
.label
.as_ref()
.map(|label| String::from_str_in(&label, bump).into_bump_str());
let event_bus = bus.clone();
let on_toggle = self.on_toggle.clone();
let is_active = self.is_active;
let row_class = style_sheet.insert(bump, css::Rule::Row);
let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16));
let (label, input) = if let Some(id) = &self.id {
let id = String::from_str_in(id, bump).into_bump_str();
(label(bump).attr("for", id), input(bump).attr("id", id))
} else {
(label(bump), input(bump))
};
let checkbox = input
.attr("type", "checkbox")
.bool_attr("checked", self.is_active)
.on("click", move |_root, vdom, _event| {
let msg = on_toggle(!is_active);
event_bus.publish(msg);
vdom.schedule_render();
})
.finish();
let toggler = span(bump).children(vec![span(bump).finish()]).finish();
label
.attr(
"class",
bumpalo::format!(in bump, "{} {}", row_class, toggler_class)
.into_bump_str(),
)
.attr(
"style",
bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width))
.into_bump_str()
)
.children(
if let Some(label) = toggler_label {
vec![
text(label),
checkbox,
toggler,
]
} else {
vec![
checkbox,
toggler,
]
}
)
.finish()
}
}
impl<'a, Message> From<Toggler<Message>> for Element<'a, Message>
where
Message: 'static,
{
fn from(toggler: Toggler<Message>) -> Element<'a, Message> {
Element::new(toggler)
}
}

View File

@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
[dependencies]
wgpu = "0.8"
wgpu_glyph = "0.12"
wgpu = "0.9"
wgpu_glyph = "0.13"
glyph_brush = "0.7"
raw-window-handle = "0.3"
log = "0.4"

View File

@ -165,33 +165,13 @@ impl Pipeline {
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::InputStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
},
wgpu::VertexAttribute {
shader_location: 2,
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 2,
},
wgpu::VertexAttribute {
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 4,
},
wgpu::VertexAttribute {
shader_location: 4,
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 6,
},
wgpu::VertexAttribute {
shader_location: 5,
format: wgpu::VertexFormat::Sint32,
offset: 4 * 8,
},
],
attributes: &wgpu::vertex_attr_array!(
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Float32x2,
5 => Sint32,
),
},
],
},

View File

@ -87,38 +87,14 @@ impl Pipeline {
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<layer::Quad>() as u64,
step_mode: wgpu::InputStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
},
wgpu::VertexAttribute {
shader_location: 2,
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 2,
},
wgpu::VertexAttribute {
shader_location: 3,
format: wgpu::VertexFormat::Float32x4,
offset: 4 * (2 + 2),
},
wgpu::VertexAttribute {
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
offset: 4 * (2 + 2 + 4),
},
wgpu::VertexAttribute {
shader_location: 5,
format: wgpu::VertexFormat::Float32,
offset: 4 * (2 + 2 + 4 + 4),
},
wgpu::VertexAttribute {
shader_location: 6,
format: wgpu::VertexFormat::Float32,
offset: 4 * (2 + 2 + 4 + 4 + 1),
},
],
attributes: &wgpu::vertex_attr_array!(
1 => Float32x2,
2 => Float32x2,
3 => Float32x4,
4 => Float32x4,
5 => Float32,
6 => Float32,
),
},
],
},

View File

@ -150,20 +150,12 @@ impl Pipeline {
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Vertex2D>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[
attributes: &wgpu::vertex_attr_array!(
// Position
wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
},
0 => Float32x2,
// Color
wgpu::VertexAttribute {
shader_location: 1,
format: wgpu::VertexFormat::Float32x4,
offset: 4 * 2,
},
],
1 => Float32x4,
),
}],
},
fragment: Some(wgpu::FragmentState {

View File

@ -20,6 +20,7 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@ -45,6 +46,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[cfg(feature = "canvas")]

View File

@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
pub use iced_graphics::pane_grid::{

View File

@ -0,0 +1,9 @@
//! Show toggle controls using togglers.
use crate::Renderer;
pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;

View File

@ -14,11 +14,15 @@ categories = ["gui"]
debug = ["iced_native/debug"]
[dependencies]
winit = "0.24"
window_clipboard = "0.2"
log = "0.4"
thiserror = "1.0"
[dependencies.winit]
version = "0.25"
git = "https://github.com/iced-rs/winit"
rev = "9c358959ed99736566d50a511b03d2fed3aac2ae"
[dependencies.iced_native]
version = "0.4"
path = "../native"

View File

@ -310,6 +310,16 @@ async fn run_instance<A, E, C>(
window.request_redraw();
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
)) => {
use iced_native::event;
events.push(iced_native::Event::PlatformSpecific(
event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
url,
)),
));
}
event::Event::UserEvent(message) => {
messages.push(message);
}