Add `selected_icon` to `IconButton` (#3477)

Marshall Bowers created

This PR adds a new `selected_icon` method to `IconButton`.

This can be used to set a different icon that should be rendered when
the `IconButton` is selected.

Release Notes:

- N/A

Change summary

crates/ui2/src/components/button/button_like.rs  | 12 +++++++++---
crates/ui2/src/components/button/icon_button.rs  | 14 +++++++++++++-
crates/ui2/src/components/stories/icon_button.rs |  8 ++++++++
3 files changed, 30 insertions(+), 4 deletions(-)

Detailed changes

crates/ui2/src/components/button/button_like.rs 🔗

@@ -5,20 +5,23 @@ use crate::h_stack;
 use crate::prelude::*;
 
 pub trait ButtonCommon: Clickable + Disableable {
-    /// A unique element id to help identify the button.
+    /// A unique element ID to identify the button.
     fn id(&self) -> &ElementId;
+
     /// The visual style of the button.
     ///
-    /// Mosty commonly will be `ButtonStyle::Subtle`, or `ButtonStyle::Filled`
+    /// Mosty commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
     /// for an emphasized button.
     fn style(self, style: ButtonStyle) -> Self;
+
     /// The size of the button.
     ///
     /// Most buttons will use the default size.
     ///
-    /// ButtonSize can also be used to help build  non-button elements
+    /// [`ButtonSize`] can also be used to help build non-button elements
     /// that are consistently sized with buttons.
     fn size(self, size: ButtonSize) -> Self;
+
     /// The tooltip that shows when a user hovers over the button.
     ///
     /// Nearly all interactable elements should have a tooltip. Some example
@@ -31,15 +34,18 @@ pub enum ButtonStyle {
     /// A filled button with a solid background color. Provides emphasis versus
     /// the more common subtle button.
     Filled,
+
     /// 🚧 Under construction 🚧
     ///
     /// Used to emphasize a button in some way, like a selected state, or a semantic
     /// coloring like an error or success button.
     Tinted,
+
     /// The default button style, used for most buttons. Has a transparent background,
     /// but has a background color to indicate states like hover and active.
     #[default]
     Subtle,
+
     /// Used for buttons that only change forground color on hover and active states.
     ///
     /// TODO: Better docs for this.

crates/ui2/src/components/button/icon_button.rs 🔗

@@ -9,6 +9,7 @@ pub struct IconButton {
     icon: Icon,
     icon_size: IconSize,
     icon_color: Color,
+    selected_icon: Option<Icon>,
 }
 
 impl IconButton {
@@ -18,6 +19,7 @@ impl IconButton {
             icon,
             icon_size: IconSize::default(),
             icon_color: Color::Default,
+            selected_icon: None,
         }
     }
 
@@ -31,6 +33,11 @@ impl IconButton {
         self
     }
 
+    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+        self.selected_icon = icon.into();
+        self
+    }
+
     pub fn action(self, action: Box<dyn Action>) -> Self {
         self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
     }
@@ -85,6 +92,11 @@ impl RenderOnce for IconButton {
     type Rendered = ButtonLike;
 
     fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        let icon = self
+            .selected_icon
+            .filter(|_| self.base.selected)
+            .unwrap_or(self.icon);
+
         let icon_color = if self.base.disabled {
             Color::Disabled
         } else if self.base.selected {
@@ -94,7 +106,7 @@ impl RenderOnce for IconButton {
         };
 
         self.base.child(
-            IconElement::new(self.icon)
+            IconElement::new(icon)
                 .size(self.icon_size)
                 .color(icon_color),
         )

crates/ui2/src/components/stories/icon_button.rs 🔗

@@ -20,6 +20,14 @@ impl Render for IconButtonStory {
                     .w_8()
                     .child(IconButton::new("icon_a", Icon::Hash).selected(true)),
             )
+            .child(Story::label("Selected with `selected_icon`"))
+            .child(
+                div().w_8().child(
+                    IconButton::new("icon_a", Icon::AudioOn)
+                        .selected(true)
+                        .selected_icon(Icon::AudioOff),
+                ),
+            )
             .child(Story::label("Disabled"))
             .child(
                 div()