1use std::borrow::Cow;
2
3use gpui::{
4 elements::{Label, MouseEventHandler, Svg},
5 platform::{CursorStyle, MouseButton},
6 scene::{CornerRadii, MouseClick},
7 Action, AnyElement, Element, EventContext, View, ViewContext,
8};
9use workspace::searchable::Direction;
10
11use crate::{
12 mode::{SearchMode, Side},
13 SelectNextMatch, SelectPrevMatch,
14};
15
16pub(super) fn render_close_button<V: View>(
17 tooltip: &'static str,
18 theme: &theme::Search,
19 cx: &mut ViewContext<V>,
20 on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
21 dismiss_action: Option<Box<dyn Action>>,
22) -> AnyElement<V> {
23 let tooltip_style = theme::current(cx).tooltip.clone();
24
25 enum CloseButton {}
26 MouseEventHandler::new::<CloseButton, _>(0, cx, |state, _| {
27 let style = theme.dismiss_button.style_for(state);
28 Svg::new("icons/x_mark_8.svg")
29 .with_color(style.color)
30 .constrained()
31 .with_width(style.icon_width)
32 .aligned()
33 .contained()
34 .with_style(style.container)
35 .constrained()
36 .with_height(theme.search_bar_row_height)
37 })
38 .on_click(MouseButton::Left, on_click)
39 .with_cursor_style(CursorStyle::PointingHand)
40 .with_tooltip::<CloseButton>(0, tooltip.to_string(), dismiss_action, tooltip_style, cx)
41 .into_any()
42}
43
44pub(super) fn render_nav_button<V: View>(
45 icon: &'static str,
46 direction: Direction,
47 active: bool,
48 on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
49 cx: &mut ViewContext<V>,
50) -> AnyElement<V> {
51 let action: Box<dyn Action>;
52 let tooltip;
53
54 match direction {
55 Direction::Prev => {
56 action = Box::new(SelectPrevMatch);
57 tooltip = "Select Previous Match";
58 }
59 Direction::Next => {
60 action = Box::new(SelectNextMatch);
61 tooltip = "Select Next Match";
62 }
63 };
64 let tooltip_style = theme::current(cx).tooltip.clone();
65 let cursor_style = if active {
66 CursorStyle::PointingHand
67 } else {
68 CursorStyle::default()
69 };
70 enum NavButton {}
71 MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
72 let theme = theme::current(cx);
73 let style = theme
74 .search
75 .nav_button
76 .in_state(active)
77 .style_for(state)
78 .clone();
79 let mut container_style = style.container.clone();
80 let label = Label::new(icon, style.label.clone()).aligned().contained();
81 container_style.corner_radii = match direction {
82 Direction::Prev => CornerRadii {
83 bottom_right: 0.,
84 top_right: 0.,
85 ..container_style.corner_radii
86 },
87 Direction::Next => CornerRadii {
88 bottom_left: 0.,
89 top_left: 0.,
90 ..container_style.corner_radii
91 },
92 };
93 if direction == Direction::Prev {
94 // Remove right border so that when both Next and Prev buttons are
95 // next to one another, there's no double border between them.
96 container_style.border.right = false;
97 }
98 label.with_style(container_style)
99 })
100 .on_click(MouseButton::Left, on_click)
101 .with_cursor_style(cursor_style)
102 .with_tooltip::<NavButton>(
103 direction as usize,
104 tooltip.to_string(),
105 Some(action),
106 tooltip_style,
107 cx,
108 )
109 .into_any()
110}
111
112pub(crate) fn render_search_mode_button<V: View>(
113 mode: SearchMode,
114 is_active: bool,
115 on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
116 cx: &mut ViewContext<V>,
117) -> AnyElement<V> {
118 let tooltip_style = theme::current(cx).tooltip.clone();
119 enum SearchModeButton {}
120 MouseEventHandler::new::<SearchModeButton, _>(mode.region_id(), cx, |state, cx| {
121 let theme = theme::current(cx);
122 let mut style = theme
123 .search
124 .mode_button
125 .in_state(is_active)
126 .style_for(state)
127 .clone();
128 style.container.border.left = mode.border_left();
129 style.container.border.right = mode.border_right();
130
131 let label = Label::new(mode.label(), style.text.clone())
132 .aligned()
133 .contained();
134 let mut container_style = style.container.clone();
135 if let Some(button_side) = mode.button_side() {
136 if button_side == Side::Left {
137 container_style.corner_radii = CornerRadii {
138 bottom_right: 0.,
139 top_right: 0.,
140 ..container_style.corner_radii
141 };
142 label.with_style(container_style)
143 } else {
144 container_style.corner_radii = CornerRadii {
145 bottom_left: 0.,
146 top_left: 0.,
147 ..container_style.corner_radii
148 };
149 label.with_style(container_style)
150 }
151 } else {
152 container_style.corner_radii = CornerRadii::default();
153 label.with_style(container_style)
154 }
155 .constrained()
156 .with_height(theme.search.search_bar_row_height)
157 })
158 .on_click(MouseButton::Left, on_click)
159 .with_cursor_style(CursorStyle::PointingHand)
160 .with_tooltip::<SearchModeButton>(
161 mode.region_id(),
162 mode.tooltip_text().to_owned(),
163 Some(mode.activate_action()),
164 tooltip_style,
165 cx,
166 )
167 .into_any()
168}
169
170pub(crate) fn render_option_button_icon<V: View>(
171 is_active: bool,
172 icon: &'static str,
173 id: usize,
174 label: impl Into<Cow<'static, str>>,
175 action: Box<dyn Action>,
176 on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
177 cx: &mut ViewContext<V>,
178) -> AnyElement<V> {
179 let tooltip_style = theme::current(cx).tooltip.clone();
180 MouseEventHandler::new::<V, _>(id, cx, |state, cx| {
181 let theme = theme::current(cx);
182 let style = theme
183 .search
184 .option_button
185 .in_state(is_active)
186 .style_for(state);
187 Svg::new(icon)
188 .with_color(style.color.clone())
189 .constrained()
190 .with_width(style.icon_width)
191 .contained()
192 .with_style(style.container)
193 .constrained()
194 .with_height(theme.search.option_button_height)
195 .with_width(style.button_width)
196 })
197 .on_click(MouseButton::Left, on_click)
198 .with_cursor_style(CursorStyle::PointingHand)
199 .with_tooltip::<V>(id, label, Some(action), tooltip_style, cx)
200 .into_any()
201}