Skip to main content

journey/widgets/
search_bar.rs

1//! A one-line search field with a leading "Find:" label.
2//!
3//! Wraps a saudade [`TextInput`] and draws a label to its left, so the
4//! toolbar reads clearly without needing a horizontal layout container. The
5//! app polls [`SearchBar::text`] after each event to drive live filtering.
6
7use saudade::{Color, Event, EventCtx, Painter, Rect, TextInput, Theme, Widget};
8
9const LABEL_W: i32 = 44;
10const PAD: i32 = 4;
11
12pub struct SearchBar {
13    bounds: Rect,
14    input: TextInput,
15    label: String,
16}
17
18impl SearchBar {
19    pub fn new(rect: Rect) -> Self {
20        let mut me = Self {
21            bounds: rect,
22            input: TextInput::new(Rect::new(0, 0, 0, 0)),
23            label: "Find:".to_string(),
24        };
25        me.relayout();
26        me
27    }
28
29    /// Current query text.
30    pub fn text(&self) -> String {
31        self.input.text()
32    }
33
34    /// Clear the query.
35    pub fn clear(&mut self) {
36        self.input.set_text("");
37    }
38
39    fn relayout(&mut self) {
40        let x = self.bounds.x + LABEL_W;
41        let input_rect = Rect::new(
42            x,
43            self.bounds.y + PAD,
44            (self.bounds.right() - x - PAD).max(0),
45            (self.bounds.h - PAD * 2).max(0),
46        );
47        self.input.layout(input_rect);
48    }
49}
50
51impl Widget for SearchBar {
52    fn bounds(&self) -> Rect {
53        self.bounds
54    }
55
56    fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
57        painter.fill_rect(self.bounds, theme.face);
58        let label_y = self.bounds.y + (self.bounds.h - theme.font_size as i32) / 2 - 1;
59        painter.text(
60            self.bounds.x + PAD,
61            label_y,
62            &self.label,
63            theme.font_size,
64            theme.text,
65        );
66        // A thin etched line under the bar separates it from the list below.
67        painter.h_line(
68            self.bounds.x,
69            self.bounds.bottom() - 1,
70            self.bounds.w,
71            Color::MID_GRAY,
72        );
73        self.input.paint(painter, theme);
74    }
75
76    fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
77        self.input.event(event, ctx);
78    }
79
80    fn captures_pointer(&self) -> bool {
81        self.input.captures_pointer()
82    }
83
84    fn focusable(&self) -> bool {
85        self.input.focusable()
86    }
87
88    fn set_focused(&mut self, focused: bool) {
89        self.input.set_focused(focused);
90    }
91
92    fn focus_first(&mut self) -> bool {
93        self.input.focus_first()
94    }
95
96    fn layout(&mut self, bounds: Rect) {
97        self.bounds = bounds;
98        self.relayout();
99    }
100
101    fn wants_ticks(&self) -> bool {
102        self.input.wants_ticks()
103    }
104}