Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement Toggler widget for iced_native #535

Merged
merged 14 commits into from
Jun 3, 2021
73 changes: 64 additions & 9 deletions examples/styling/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)]
Expand All @@ -27,6 +28,7 @@ enum Message {
ButtonPressed,
SliderChanged(f32),
CheckboxToggled(bool),
TogglerToggled(bool),
}

impl Sandbox for Styling {
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down
32 changes: 31 additions & 1 deletion examples/tour/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -206,6 +209,9 @@ enum Step {
Radio {
selection: Option<Language>,
},
Toggler {
can_continue: bool,
},
Image {
width: u16,
slider: slider::State,
Expand All @@ -232,6 +238,7 @@ pub enum StepMessage {
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
TogglerChanged(bool),
}

impl<'a> Step {
Expand Down Expand Up @@ -287,13 +294,19 @@ impl<'a> Step {
*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",
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions glow/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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")]
Expand Down
9 changes: 9 additions & 0 deletions glow/src/widget/toggler.rs
Original file line number Diff line number Diff line change
@@ -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>;
3 changes: 3 additions & 0 deletions graphics/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
99 changes: 99 additions & 0 deletions graphics/src/widget/toggler.rs
Original file line number Diff line number Diff line change
@@ -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()
},
)
}
}
Loading