input: Add mask_toggle button to toggle masked state to Input. (#758)

This commit is contained in:
Jason Lee 2025-03-31 22:57:07 +08:00 committed by GitHub
parent 3357197f60
commit 88b5e5c023
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 9 deletions

View file

@ -33,7 +33,7 @@ pub struct InputStory {
number_input1: Entity<NumberInput>, number_input1: Entity<NumberInput>,
number_input2: Entity<NumberInput>, number_input2: Entity<NumberInput>,
number_input2_value: u64, number_input2_value: u64,
mash_input: Entity<TextInput>, mask_input: Entity<TextInput>,
disabled_input: Entity<TextInput>, disabled_input: Entity<TextInput>,
prefix_input1: Entity<TextInput>, prefix_input1: Entity<TextInput>,
suffix_input1: Entity<TextInput>, suffix_input1: Entity<TextInput>,
@ -138,8 +138,10 @@ impl InputStory {
.detach(); .detach();
let mask_input = cx.new(|cx| { let mask_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx).cleanable(); let mut input = TextInput::new(window, cx)
input.set_masked(true, window, cx); .masked(true)
.mask_toggle()
.cleanable();
input.set_text("this-is-password", window, cx); input.set_text("this-is-password", window, cx);
input input
}); });
@ -183,7 +185,7 @@ impl InputStory {
number_input1_value, number_input1_value,
number_input2, number_input2,
number_input2_value: 0, number_input2_value: 0,
mash_input: mask_input, mask_input,
disabled_input: cx.new(|cx| { disabled_input: cx.new(|cx| {
let mut input = TextInput::new(window, cx); let mut input = TextInput::new(window, cx);
input.set_text("This is disabled input", window, cx); input.set_text("This is disabled input", window, cx);
@ -375,7 +377,7 @@ impl FocusableCycle for InputStory {
self.input1.focus_handle(cx), self.input1.focus_handle(cx),
self.input2.focus_handle(cx), self.input2.focus_handle(cx),
self.disabled_input.focus_handle(cx), self.disabled_input.focus_handle(cx),
self.mash_input.focus_handle(cx), self.mask_input.focus_handle(cx),
self.prefix_input1.focus_handle(cx), self.prefix_input1.focus_handle(cx),
self.both_input1.focus_handle(cx), self.both_input1.focus_handle(cx),
self.suffix_input1.focus_handle(cx), self.suffix_input1.focus_handle(cx),
@ -450,7 +452,7 @@ impl Render for InputStory {
.child( .child(
section("Input State", cx) section("Input State", cx)
.child(self.disabled_input.clone()) .child(self.disabled_input.clone())
.child(self.mash_input.clone()), .child(self.mask_input.clone()),
), ),
) )
.child( .child(

View file

@ -5,6 +5,7 @@ use crate::{
ActiveTheme as _, Icon, IconName, Sizable as _, ActiveTheme as _, Icon, IconName, Sizable as _,
}; };
#[inline]
pub(crate) fn clear_button(cx: &App) -> Button { pub(crate) fn clear_button(cx: &App) -> Button {
Button::new("clean") Button::new("clean")
.icon(Icon::new(IconName::CircleX)) .icon(Icon::new(IconName::CircleX))

View file

@ -27,13 +27,14 @@ use super::change::Change;
use super::element::TextElement; use super::element::TextElement;
use super::number_input; use super::number_input;
use crate::button::{Button, ButtonVariants as _};
use crate::history::History; use crate::history::History;
use crate::indicator::Indicator; use crate::indicator::Indicator;
use crate::input::clear_button; use crate::input::clear_button;
use crate::scroll::{Scrollbar, ScrollbarAxis, ScrollbarState}; use crate::scroll::{Scrollbar, ScrollbarAxis, ScrollbarState};
use crate::Size;
use crate::StyledExt; use crate::StyledExt;
use crate::{ActiveTheme, Root}; use crate::{ActiveTheme, Root};
use crate::{IconName, Size};
use crate::{Sizable, StyleSized}; use crate::{Sizable, StyleSized};
actions!( actions!(
@ -220,6 +221,7 @@ pub struct TextInput {
pub(super) selecting: bool, pub(super) selecting: bool,
pub(super) disabled: bool, pub(super) disabled: bool,
pub(super) masked: bool, pub(super) masked: bool,
pub(super) mask_toggle: bool,
pub(super) appearance: bool, pub(super) appearance: bool,
pub(super) cleanable: bool, pub(super) cleanable: bool,
pub(super) size: Size, pub(super) size: Size,
@ -279,6 +281,7 @@ impl TextInput {
selecting: false, selecting: false,
disabled: false, disabled: false,
masked: false, masked: false,
mask_toggle: false,
appearance: true, appearance: true,
cleanable: false, cleanable: false,
loading: false, loading: false,
@ -520,12 +523,24 @@ impl TextInput {
cx.notify(); cx.notify();
} }
/// Set with masked state.
pub fn masked(mut self, masked: bool) -> Self {
self.masked = masked;
self
}
/// Set the masked state of the input field. /// Set the masked state of the input field.
pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) { pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
self.masked = masked; self.masked = masked;
cx.notify(); cx.notify();
} }
/// Set to enable toggle button for mask state.
pub fn mask_toggle(mut self) -> Self {
self.mask_toggle = true;
self
}
/// Set the prefix element of the input field. /// Set the prefix element of the input field.
pub fn set_prefix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>) pub fn set_prefix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
where where
@ -1447,6 +1462,35 @@ impl TextInput {
.map(|p| p.is_match(new_text)) .map(|p| p.is_match(new_text))
.unwrap_or(true) .unwrap_or(true)
} }
fn render_toggle_mask_button(
&self,
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
if !self.mask_toggle {
return None;
}
Some(
Button::new("toggle-mask")
.icon(IconName::Eye)
.xsmall()
.ghost()
.on_mouse_down(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
this.set_masked(false, window, cx);
}),
)
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
this.set_masked(true, window, cx);
}),
),
)
}
} }
impl Sizable for TextInput { impl Sizable for TextInput {
@ -1642,8 +1686,8 @@ impl Render for TextInput {
let focused = self.focus_handle.is_focused(window); let focused = self.focus_handle.is_focused(window);
let mut gap_x = match self.size { let mut gap_x = match self.size {
Size::Small => px(4.), Size::Small => px(4.),
Size::Large => px(12.), Size::Large => px(8.),
_ => px(8.), _ => px(4.),
}; };
if self.no_gap { if self.no_gap {
gap_x = px(0.); gap_x = px(0.);
@ -1738,6 +1782,7 @@ impl Render for TextInput {
.when(self.loading, |this| { .when(self.loading, |this| {
this.child(Indicator::new().color(cx.theme().muted_foreground)) this.child(Indicator::new().color(cx.theme().muted_foreground))
}) })
.children(self.render_toggle_mask_button(window, cx))
.when( .when(
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(), self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(),
|this| this.child(clear_button(cx).on_click(cx.listener(Self::clean))), |this| this.child(clear_button(cx).on_click(cx.listener(Self::clean))),