1use crate::svg::load_svg_bytes;
2use crate::{
3 include_labeled_bytes, Button, Color, ControlState, EdgeInsets, EventCtx, GfxCtx, MultiKey,
4 Outcome, RewriteColor, ScreenDims, ScreenPt, Text, TextSpan, Widget, WidgetImpl, WidgetOutput,
5};
6
7pub struct Toggle {
8 pub(crate) enabled: bool,
9 pub(crate) btn: Button,
10 other_btn: Button,
11}
12
13impl Toggle {
14 pub fn new_widget(enabled: bool, false_btn: Button, true_btn: Button) -> Widget {
15 if enabled {
16 Widget::new(Box::new(Toggle {
17 enabled,
18 btn: true_btn,
19 other_btn: false_btn,
20 }))
21 } else {
22 Widget::new(Box::new(Toggle {
23 enabled,
24 btn: false_btn,
25 other_btn: true_btn,
26 }))
27 }
28 }
29
30 pub fn switch<MK: Into<Option<MultiKey>>>(
31 ctx: &EventCtx,
32 label: &str,
33 hotkey: MK,
34 enabled: bool,
35 ) -> Widget {
36 let mut buttons = ctx
37 .style()
38 .btn_plain
39 .text(label)
40 .image_color(RewriteColor::NoOp, ControlState::Default);
42
43 if let Some(hotkey) = hotkey.into() {
44 buttons = buttons.hotkey(hotkey);
45 }
46
47 let (off_batch, off_bounds) = {
48 let (label, bytes) = include_labeled_bytes!("../../icons/switch_off.svg");
49 let (batch, bounds) = load_svg_bytes(ctx.prerender, label, bytes).expect("invalid SVG");
50 let batch = batch
51 .color(RewriteColor::Change(Color::WHITE, ctx.style.btn_solid.bg))
52 .color(RewriteColor::Change(Color::BLACK, ctx.style.btn_solid.fg));
53 (batch, bounds)
54 };
55 let (on_batch, on_bounds) = {
56 let (label, bytes) = include_labeled_bytes!("../../icons/switch_on.svg");
57 let (batch, bounds) = load_svg_bytes(ctx.prerender, label, bytes).expect("invalid SVG");
58 let batch = batch
59 .color(RewriteColor::Change(Color::WHITE, ctx.style.btn_solid.bg))
60 .color(RewriteColor::Change(Color::BLACK, ctx.style.btn_solid.fg));
61 (batch, bounds)
62 };
63
64 let off_button = buttons
65 .clone()
66 .image_batch(off_batch, off_bounds)
67 .build(ctx, label);
68
69 let on_button = buttons.image_batch(on_batch, on_bounds).build(ctx, label);
70
71 Toggle::new_widget(enabled, off_button, on_button).named(label)
72 }
73
74 pub fn checkbox<MK: Into<Option<MultiKey>>>(
75 ctx: &EventCtx,
76 label: &str,
77 hotkey: MK,
78 enabled: bool,
79 ) -> Widget {
80 let mut false_btn = ctx
81 .style()
82 .btn_plain
83 .icon_bytes(include_labeled_bytes!("../../icons/checkbox_unchecked.svg"))
84 .image_color(
85 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
86 ControlState::Default,
87 )
88 .image_color(
89 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
90 ControlState::Hovered,
91 )
92 .image_color(
93 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
94 ControlState::Disabled,
95 )
96 .label_text(label);
97
98 if let Some(hotkey) = hotkey.into() {
99 false_btn = false_btn.hotkey(hotkey);
100 }
101
102 let true_btn = false_btn
103 .clone()
104 .image_bytes(include_labeled_bytes!("../../icons/checkbox_checked.svg"));
105
106 Toggle::new_widget(
107 enabled,
108 false_btn.build(ctx, label),
109 true_btn.build(ctx, label),
110 )
111 .named(label)
112 }
113
114 pub fn custom_checkbox<MK: Into<Option<MultiKey>>>(
115 ctx: &EventCtx,
116 action: &str,
117 spans: Vec<TextSpan>,
118 hotkey: MK,
119 enabled: bool,
120 ) -> Widget {
121 let mut false_btn = ctx
122 .style()
123 .btn_plain
124 .icon_bytes(include_labeled_bytes!("../../icons/checkbox_unchecked.svg"))
125 .image_color(
126 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
127 ControlState::Default,
128 )
129 .image_color(
130 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
131 ControlState::Hovered,
132 )
133 .image_color(
134 RewriteColor::Change(Color::BLACK, ctx.style().icon_fg),
135 ControlState::Disabled,
136 )
137 .label_styled_text(Text::from_all(spans), ControlState::Default);
138
139 if let Some(hotkey) = hotkey.into() {
140 false_btn = false_btn.hotkey(hotkey);
141 }
142
143 let true_btn = false_btn
144 .clone()
145 .image_bytes(include_labeled_bytes!("../../icons/checkbox_checked.svg"));
146
147 Toggle::new_widget(
148 enabled,
149 false_btn.build(ctx, action),
150 true_btn.build(ctx, action),
151 )
152 .named(action)
153 }
154
155 pub fn colored_checkbox(ctx: &EventCtx, label: &str, color: Color, enabled: bool) -> Widget {
156 let buttons = ctx.style().btn_plain.btn().label_text(label).padding(4.0);
157
158 let false_btn = buttons
159 .clone()
160 .image_bytes(include_labeled_bytes!(
161 "../../icons/checkbox_no_border_unchecked.svg"
162 ))
163 .image_color(
164 RewriteColor::Change(Color::BLACK, color.alpha(0.3)),
165 ControlState::Default,
166 );
167
168 let true_btn = buttons
169 .image_bytes(include_labeled_bytes!(
170 "../../icons/checkbox_no_border_checked.svg"
171 ))
172 .image_color(
173 RewriteColor::Change(Color::BLACK, color),
174 ControlState::Default,
175 );
176
177 Toggle::new_widget(
178 enabled,
179 false_btn.build(ctx, label),
180 true_btn.build(ctx, label),
181 )
182 .named(label)
183 }
184
185 pub fn choice<MK: Into<Option<MultiKey>>>(
187 ctx: &EventCtx,
188 action: &str,
189 left_label: &str,
190 right_label: &str,
191 hotkey: MK,
192 enabled: bool,
193 ) -> Widget {
194 let mut toggle_left_button = ctx
195 .style()
196 .btn_plain
197 .btn()
198 .image_dims(ScreenDims::new(40.0, 40.0))
199 .padding(4)
200 .image_color(RewriteColor::NoOp, ControlState::Default);
202
203 if let Some(hotkey) = hotkey.into() {
204 toggle_left_button = toggle_left_button.hotkey(hotkey);
205 }
206
207 let (left_batch, left_bounds) = {
208 let (label, bytes) = include_labeled_bytes!("../../icons/toggle_left.svg");
209 let (batch, bounds) = load_svg_bytes(ctx.prerender, label, bytes).expect("invalid SVG");
210 let batch = batch
211 .color(RewriteColor::Change(Color::WHITE, ctx.style.btn_solid.bg))
212 .color(RewriteColor::Change(Color::BLACK, ctx.style.btn_solid.fg));
213 (batch, bounds)
214 };
215 let (right_batch, right_bounds) = {
216 let (label, bytes) = include_labeled_bytes!("../../icons/toggle_right.svg");
217 let (batch, bounds) = load_svg_bytes(ctx.prerender, label, bytes).expect("invalid SVG");
218 let batch = batch
219 .color(RewriteColor::Change(Color::WHITE, ctx.style.btn_solid.bg))
220 .color(RewriteColor::Change(Color::BLACK, ctx.style.btn_solid.fg));
221 (batch, bounds)
222 };
223
224 let toggle_right_button = toggle_left_button
225 .clone()
226 .image_batch(right_batch, right_bounds);
227
228 let toggle_left_button = toggle_left_button
229 .clone()
230 .image_batch(left_batch, left_bounds);
231
232 let left_text_button = ctx
233 .style()
234 .btn_plain
235 .text(left_label)
236 .padding(EdgeInsets {
238 left: 2.0,
239 right: 2.0,
240 top: 8.0,
241 bottom: 14.0,
242 })
243 .disabled(true)
245 .label_color(ctx.style().btn_outline.fg, ControlState::Disabled)
246 .bg_color(Color::CLEAR, ControlState::Disabled);
247 let right_text_button = left_text_button.clone().label_text(right_label);
248 Widget::row(vec![
249 left_text_button.build_def(ctx).centered_vert(),
250 Toggle::new_widget(
251 enabled,
252 toggle_right_button.build(ctx, right_label),
253 toggle_left_button.build(ctx, left_label),
254 )
255 .named(action)
256 .centered_vert(),
257 right_text_button.build_def(ctx).centered_vert(),
258 ])
259 }
260}
261
262impl WidgetImpl for Toggle {
263 fn get_dims(&self) -> ScreenDims {
264 self.btn.get_dims()
265 }
266
267 fn set_pos(&mut self, top_left: ScreenPt) {
268 self.btn.set_pos(top_left);
269 }
270
271 fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
272 self.btn.event(ctx, output);
273 if let Outcome::Clicked(_) = output.outcome {
274 output.outcome = Outcome::Changed(self.btn.action.clone());
276 std::mem::swap(&mut self.btn, &mut self.other_btn);
277 self.btn.set_pos(self.other_btn.top_left);
278 self.enabled = !self.enabled;
279 output.redo_layout = true;
280 }
281 }
282
283 fn draw(&self, g: &mut GfxCtx) {
284 self.btn.draw(g);
285 }
286}