rosace_widgets/tree/
search_bar.rs1use std::sync::{Arc, OnceLock};
9
10use rosace_core::types::Size;
11
12use super::{BoxedWidget, Children, LayoutCtx, PaintCtx, Widget};
13
14pub struct SearchBar {
15 value: String,
16 placeholder: String,
17 width: Option<f32>,
18 height: f32,
19 on_change: Option<Arc<dyn Fn(String) + Send + Sync>>,
20 on_clear: Option<Arc<dyn Fn() + Send + Sync>>,
21 inner: OnceLock<BoxedWidget>,
22}
23
24impl SearchBar {
25 pub fn new() -> Self {
26 Self {
27 value: String::new(),
28 placeholder: "Search\u{2026}".to_string(),
29 width: None,
30 height: 36.0,
31 on_change: None,
32 on_clear: None,
33 inner: OnceLock::new(),
34 }
35 }
36 pub fn value(mut self, v: impl Into<String>) -> Self { self.value = v.into(); self }
37 pub fn placeholder(mut self, p: impl Into<String>) -> Self { self.placeholder = p.into(); self }
38 pub fn width(mut self, w: f32) -> Self { self.width = Some(w); self }
39 pub fn height(mut self, h: f32) -> Self { self.height = h; self }
40 pub fn on_change(mut self, f: impl Fn(String) + Send + Sync + 'static) -> Self {
41 self.on_change = Some(Arc::new(f)); self
42 }
43 pub fn on_clear(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
45 self.on_clear = Some(Arc::new(f)); self
46 }
47
48 fn inner(&self) -> &BoxedWidget {
49 self.inner.get_or_init(|| {
50 let mut input = super::TextInput::new()
51 .value(self.value.clone())
52 .placeholder(self.placeholder.clone())
53 .height(self.height)
54 .leading(super::Icon::new(super::IconKind::Search).size(18.0));
55 if let Some(w) = self.width { input = input.width(w); }
56 if let Some(f) = &self.on_change {
57 let f = Arc::clone(f);
58 input = input.on_change(move |v| f(v));
59 }
60 if let Some(clear) = &self.on_clear {
61 if !self.value.is_empty() {
62 let clear = Arc::clone(clear);
63 input = input
64 .trailing(super::Text::new("\u{00d7}").size(16.0))
65 .on_trailing(move || clear());
66 }
67 }
68 Box::new(input)
69 })
70 }
71}
72
73impl Default for SearchBar {
74 fn default() -> Self { Self::new() }
75}
76
77impl Widget for SearchBar {
78 fn children(&self) -> Children<'_> { Children::One(self.inner().as_ref()) }
79 fn layout(&self, ctx: &LayoutCtx) -> Size { self.inner().layout(ctx) }
80 fn paint(&self, ctx: &mut PaintCtx) { self.inner().paint(ctx); }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use rosace_layout::Constraints;
87
88 #[test]
89 fn search_bar_is_a_text_input_with_a_leading_icon() {
90 let font = rosace_render::FontCache::embedded();
91 let theme = rosace_theme::built_in::dark_theme();
92 let ctx = LayoutCtx::new(Constraints::loose(500.0, 60.0), &font, &theme);
93 let sb = SearchBar::new().width(200.0);
94 assert_eq!(sb.layout(&ctx).width, 200.0);
97 }
98}