Skip to content

Commit 397a5c0

Browse files
authored
Merge pull request #535 from Kaiden42/toggler
Implement `Toggler` widget for iced_native
2 parents 1dce929 + d3d6f3e commit 397a5c0

File tree

17 files changed

+797
-14
lines changed

17 files changed

+797
-14
lines changed

examples/styling/src/main.rs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use iced::{
22
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
33
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
4-
Scrollable, Settings, Slider, Space, Text, TextInput,
4+
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
55
};
66

77
pub fn main() -> iced::Result {
@@ -17,7 +17,8 @@ struct Styling {
1717
button: button::State,
1818
slider: slider::State,
1919
slider_value: f32,
20-
toggle_value: bool,
20+
checkbox_value: bool,
21+
toggler_value: bool,
2122
}
2223

2324
#[derive(Debug, Clone)]
@@ -27,6 +28,7 @@ enum Message {
2728
ButtonPressed,
2829
SliderChanged(f32),
2930
CheckboxToggled(bool),
31+
TogglerToggled(bool),
3032
}
3133

3234
impl Sandbox for Styling {
@@ -46,7 +48,8 @@ impl Sandbox for Styling {
4648
Message::InputChanged(value) => self.input_value = value,
4749
Message::ButtonPressed => {}
4850
Message::SliderChanged(value) => self.slider_value = value,
49-
Message::CheckboxToggled(value) => self.toggle_value = value,
51+
Message::CheckboxToggled(value) => self.checkbox_value = value,
52+
Message::TogglerToggled(value) => self.toggler_value = value,
5053
}
5154
}
5255

@@ -101,11 +104,19 @@ impl Sandbox for Styling {
101104
.push(Text::new("You did it!"));
102105

103106
let checkbox = Checkbox::new(
104-
self.toggle_value,
105-
"Toggle me!",
107+
self.checkbox_value,
108+
"Check me!",
106109
Message::CheckboxToggled,
107110
)
108-
.width(Length::Fill)
111+
.style(self.theme);
112+
113+
let toggler = Toggler::new(
114+
self.toggler_value,
115+
String::from("Toggle me!"),
116+
Message::TogglerToggled,
117+
)
118+
.width(Length::Shrink)
119+
.spacing(10)
109120
.style(self.theme);
110121

111122
let content = Column::new()
@@ -124,7 +135,13 @@ impl Sandbox for Styling {
124135
.align_items(Align::Center)
125136
.push(scrollable)
126137
.push(Rule::vertical(38).style(self.theme))
127-
.push(checkbox),
138+
.push(
139+
Column::new()
140+
.width(Length::Shrink)
141+
.spacing(20)
142+
.push(checkbox)
143+
.push(toggler),
144+
),
128145
);
129146

130147
Container::new(content)
@@ -140,7 +157,7 @@ impl Sandbox for Styling {
140157
mod style {
141158
use iced::{
142159
button, checkbox, container, progress_bar, radio, rule, scrollable,
143-
slider, text_input,
160+
slider, text_input, toggler,
144161
};
145162

146163
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -231,6 +248,15 @@ mod style {
231248
}
232249
}
233250

251+
impl From<Theme> for Box<dyn toggler::StyleSheet> {
252+
fn from(theme: Theme) -> Self {
253+
match theme {
254+
Theme::Light => Default::default(),
255+
Theme::Dark => dark::Toggler.into(),
256+
}
257+
}
258+
}
259+
234260
impl From<Theme> for Box<dyn rule::StyleSheet> {
235261
fn from(theme: Theme) -> Self {
236262
match theme {
@@ -269,7 +295,7 @@ mod style {
269295
mod dark {
270296
use iced::{
271297
button, checkbox, container, progress_bar, radio, rule, scrollable,
272-
slider, text_input, Color,
298+
slider, text_input, toggler, Color,
273299
};
274300

275301
const SURFACE: Color = Color::from_rgb(
@@ -520,6 +546,35 @@ mod style {
520546
}
521547
}
522548

549+
pub struct Toggler;
550+
551+
impl toggler::StyleSheet for Toggler {
552+
fn active(&self, is_active: bool) -> toggler::Style {
553+
toggler::Style {
554+
background: if is_active { ACTIVE } else { SURFACE },
555+
background_border: None,
556+
foreground: if is_active { Color::WHITE } else { ACTIVE },
557+
foreground_border: None,
558+
}
559+
}
560+
561+
fn hovered(&self, is_active: bool) -> toggler::Style {
562+
toggler::Style {
563+
background: if is_active { ACTIVE } else { SURFACE },
564+
background_border: None,
565+
foreground: if is_active {
566+
Color {
567+
a: 0.5,
568+
..Color::WHITE
569+
}
570+
} else {
571+
Color { a: 0.5, ..ACTIVE }
572+
},
573+
foreground_border: None,
574+
}
575+
}
576+
}
577+
523578
pub struct Rule;
524579

525580
impl rule::StyleSheet for Rule {

examples/tour/src/main.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use iced::{
22
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
33
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
4-
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
4+
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
55
};
66

77
pub fn main() -> iced::Result {
@@ -135,6 +135,9 @@ impl Steps {
135135
color: Color::BLACK,
136136
},
137137
Step::Radio { selection: None },
138+
Step::Toggler {
139+
can_continue: false,
140+
},
138141
Step::Image {
139142
width: 300,
140143
slider: slider::State::new(),
@@ -206,6 +209,9 @@ enum Step {
206209
Radio {
207210
selection: Option<Language>,
208211
},
212+
Toggler {
213+
can_continue: bool,
214+
},
209215
Image {
210216
width: u16,
211217
slider: slider::State,
@@ -232,6 +238,7 @@ pub enum StepMessage {
232238
InputChanged(String),
233239
ToggleSecureInput(bool),
234240
DebugToggled(bool),
241+
TogglerChanged(bool),
235242
}
236243

237244
impl<'a> Step {
@@ -287,13 +294,19 @@ impl<'a> Step {
287294
*is_secure = toggle;
288295
}
289296
}
297+
StepMessage::TogglerChanged(value) => {
298+
if let Step::Toggler { can_continue, .. } = self {
299+
*can_continue = value;
300+
}
301+
}
290302
};
291303
}
292304

293305
fn title(&self) -> &str {
294306
match self {
295307
Step::Welcome => "Welcome",
296308
Step::Radio { .. } => "Radio button",
309+
Step::Toggler { .. } => "Toggler",
297310
Step::Slider { .. } => "Slider",
298311
Step::Text { .. } => "Text",
299312
Step::Image { .. } => "Image",
@@ -309,6 +322,7 @@ impl<'a> Step {
309322
match self {
310323
Step::Welcome => true,
311324
Step::Radio { selection } => *selection == Some(Language::Rust),
325+
Step::Toggler { can_continue } => *can_continue,
312326
Step::Slider { .. } => true,
313327
Step::Text { .. } => true,
314328
Step::Image { .. } => true,
@@ -324,6 +338,7 @@ impl<'a> Step {
324338
match self {
325339
Step::Welcome => Self::welcome(),
326340
Step::Radio { selection } => Self::radio(*selection),
341+
Step::Toggler { can_continue } => Self::toggler(*can_continue),
327342
Step::Slider { state, value } => Self::slider(state, *value),
328343
Step::Text {
329344
size_slider,
@@ -545,6 +560,21 @@ impl<'a> Step {
545560
))
546561
}
547562

563+
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
564+
Self::container("Toggler")
565+
.push(Text::new(
566+
"A toggler is mostly used to enable or disable something.",
567+
))
568+
.push(
569+
Container::new(Toggler::new(
570+
can_continue,
571+
String::from("Toggle me to continue..."),
572+
StepMessage::TogglerChanged,
573+
))
574+
.padding([0, 40]),
575+
)
576+
}
577+
548578
fn image(
549579
width: u16,
550580
slider: &'a mut slider::State,

glow/src/widget.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod rule;
2020
pub mod scrollable;
2121
pub mod slider;
2222
pub mod text_input;
23+
pub mod toggler;
2324
pub mod tooltip;
2425

2526
#[doc(no_inline)]
@@ -45,6 +46,8 @@ pub use slider::Slider;
4546
#[doc(no_inline)]
4647
pub use text_input::TextInput;
4748
#[doc(no_inline)]
49+
pub use toggler::Toggler;
50+
#[doc(no_inline)]
4851
pub use tooltip::Tooltip;
4952

5053
#[cfg(feature = "canvas")]

glow/src/widget/toggler.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! Show toggle controls using togglers.
2+
use crate::Renderer;
3+
4+
pub use iced_graphics::toggler::{Style, StyleSheet};
5+
6+
/// A toggler that can be toggled.
7+
///
8+
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
9+
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;

graphics/src/widget.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod scrollable;
2020
pub mod slider;
2121
pub mod svg;
2222
pub mod text_input;
23+
pub mod toggler;
2324
pub mod tooltip;
2425

2526
mod column;
@@ -50,6 +51,8 @@ pub use slider::Slider;
5051
#[doc(no_inline)]
5152
pub use text_input::TextInput;
5253
#[doc(no_inline)]
54+
pub use toggler::Toggler;
55+
#[doc(no_inline)]
5356
pub use tooltip::Tooltip;
5457

5558
pub use column::Column;

graphics/src/widget/toggler.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//! Show toggle controls using togglers.
2+
use crate::backend::{self, Backend};
3+
use crate::{Primitive, Renderer};
4+
use iced_native::mouse;
5+
use iced_native::toggler;
6+
use iced_native::Rectangle;
7+
8+
pub use iced_style::toggler::{Style, StyleSheet};
9+
10+
/// Makes sure that the border radius of the toggler looks good at every size.
11+
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
12+
13+
/// The space ratio between the background Quad and the Toggler bounds, and
14+
/// between the background Quad and foreground Quad.
15+
const SPACE_RATIO: f32 = 0.05;
16+
17+
/// A toggler that can be toggled.
18+
///
19+
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
20+
pub type Toggler<Message, Backend> =
21+
iced_native::Toggler<Message, Renderer<Backend>>;
22+
23+
impl<B> toggler::Renderer for Renderer<B>
24+
where
25+
B: Backend + backend::Text,
26+
{
27+
type Style = Box<dyn StyleSheet>;
28+
29+
const DEFAULT_SIZE: u16 = 20;
30+
31+
fn draw(
32+
&mut self,
33+
bounds: Rectangle,
34+
is_active: bool,
35+
is_mouse_over: bool,
36+
label: Option<Self::Output>,
37+
style_sheet: &Self::Style,
38+
) -> Self::Output {
39+
let style = if is_mouse_over {
40+
style_sheet.hovered(is_active)
41+
} else {
42+
style_sheet.active(is_active)
43+
};
44+
45+
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
46+
let space = SPACE_RATIO * bounds.height as f32;
47+
48+
let toggler_background_bounds = Rectangle {
49+
x: bounds.x + space,
50+
y: bounds.y + space,
51+
width: bounds.width - (2.0 * space),
52+
height: bounds.height - (2.0 * space),
53+
};
54+
55+
let toggler_background = Primitive::Quad {
56+
bounds: toggler_background_bounds,
57+
background: style.background.into(),
58+
border_radius,
59+
border_width: 1.0,
60+
border_color: style.background_border.unwrap_or(style.background),
61+
};
62+
63+
let toggler_foreground_bounds = Rectangle {
64+
x: bounds.x
65+
+ if is_active {
66+
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
67+
} else {
68+
2.0 * space
69+
},
70+
y: bounds.y + (2.0 * space),
71+
width: bounds.height - (4.0 * space),
72+
height: bounds.height - (4.0 * space),
73+
};
74+
75+
let toggler_foreground = Primitive::Quad {
76+
bounds: toggler_foreground_bounds,
77+
background: style.foreground.into(),
78+
border_radius,
79+
border_width: 1.0,
80+
border_color: style.foreground_border.unwrap_or(style.foreground),
81+
};
82+
83+
(
84+
Primitive::Group {
85+
primitives: match label {
86+
Some((l, _)) => {
87+
vec![l, toggler_background, toggler_foreground]
88+
}
89+
None => vec![toggler_background, toggler_foreground],
90+
},
91+
},
92+
if is_mouse_over {
93+
mouse::Interaction::Pointer
94+
} else {
95+
mouse::Interaction::default()
96+
},
97+
)
98+
}
99+
}

0 commit comments

Comments
 (0)