all_is_cubes_ui/vui/widgets/button/
widget.rs1use all_is_cubes::universe;
4use alloc::sync::Arc;
5use core::fmt;
6use core::hash::Hash;
7
8use exhaust::Exhaust;
9
10use all_is_cubes::arcstr::ArcStr;
11use all_is_cubes::block::Block;
12use all_is_cubes::inv::EphemeralOpaque;
13use all_is_cubes::linking;
14use all_is_cubes::listen;
15
16use crate::vui;
17use crate::vui::widgets::{BoxStyle, WidgetBlocks, WidgetTheme};
18
19use super::Action;
20
21#[derive(Clone, Debug)]
27pub(in crate::vui::widgets::button) struct ButtonCommon<St> {
28 pub(in crate::vui::widgets::button) shape: linking::Provider<St, BoxStyle>,
30
31 pub(in crate::vui::widgets::button) label: ButtonLabel,
33}
34
35impl<St: Clone + Eq + Hash + Exhaust + fmt::Debug> ButtonCommon<St> {
36 fn new(shape: &linking::Provider<St, Block>, label: ButtonLabel) -> Self {
37 let shape = shape.map(|_, base_multiblock| BoxStyle::from_nine_and_thin(base_multiblock));
38 Self { shape, label }
39 }
40}
41
42impl<St: Eq + Hash + universe::VisitHandles> universe::VisitHandles for ButtonCommon<St> {
43 fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
44 let Self { shape, label } = self;
45 shape.visit_handles(visitor);
46 label.visit_handles(visitor);
47 }
48}
49
50#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
54#[non_exhaustive]
55pub struct ButtonLabel {
56 pub icon: Option<Block>,
60
61 pub text: Option<vui::widgets::Label>,
63}
64
65impl From<Block> for ButtonLabel {
66 fn from(icon: Block) -> Self {
67 ButtonLabel {
68 icon: Some(icon),
69 text: None,
70 }
71 }
72}
73impl From<vui::widgets::Label> for ButtonLabel {
74 fn from(text: vui::widgets::Label) -> Self {
75 ButtonLabel {
76 icon: None,
77 text: Some(text),
78 }
79 }
80}
81impl From<ArcStr> for ButtonLabel {
82 fn from(string: ArcStr) -> Self {
83 ButtonLabel {
84 icon: None,
85 text: Some(vui::widgets::Label::new(string)),
86 }
87 }
88}
89
90impl universe::VisitHandles for ButtonLabel {
91 fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
92 let Self { icon, text } = self;
93 icon.visit_handles(visitor);
94 text.visit_handles(visitor);
95 }
96}
97
98#[derive(Clone, Debug)]
104pub struct ActionButton {
105 pub(in crate::vui::widgets::button) common: ButtonCommon<ButtonVisualState>,
106 pub(in crate::vui::widgets::button) action: Action,
107}
108
109impl ActionButton {
110 #[allow(missing_docs)] pub fn new(
112 label: impl Into<ButtonLabel>,
113 theme: &WidgetTheme,
114 action: impl Fn() + Send + Sync + 'static,
115 ) -> Arc<Self> {
116 Arc::new(Self {
117 common: ButtonCommon::new(
118 &theme.widget_blocks.subset(WidgetBlocks::ActionButton),
119 label.into(),
120 ),
121 action: EphemeralOpaque::new(Arc::new(action)),
122 })
123 }
124}
125
126impl vui::Layoutable for ActionButton {
127 fn requirements(&self) -> vui::LayoutRequest {
128 self.common.requirements()
129 }
130}
131
132#[derive(Clone)]
135pub struct ToggleButton<D> {
136 pub(in crate::vui::widgets::button) common: ButtonCommon<ToggleButtonVisualState>,
137 pub(in crate::vui::widgets::button) data_source: listen::DynSource<D>,
138 pub(in crate::vui::widgets::button) projection: Arc<dyn Fn(&D) -> bool + Send + Sync>,
139 pub(in crate::vui::widgets::button) action: Action,
140}
141
142impl<D: Clone + Sync + fmt::Debug> fmt::Debug for ToggleButton<D> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 let Self {
145 common,
146 data_source,
147 projection,
148 action,
149 } = self;
150 f.debug_struct("ToggleButton")
151 .field("common", common)
152 .field("data_source", data_source)
153 .field("projection(data_source)", &projection(&data_source.get()))
154 .field("action", action)
155 .finish()
156 }
157}
158
159impl<D> ToggleButton<D> {
160 #[allow(missing_docs)]
161 pub fn new(
162 data_source: listen::DynSource<D>,
163 projection: impl Fn(&D) -> bool + Send + Sync + 'static,
164 label: impl Into<ButtonLabel>,
165 theme: &WidgetTheme,
166 action: impl Fn() + Send + Sync + 'static,
167 ) -> Arc<Self> {
168 Arc::new(Self {
169 common: ButtonCommon::new(
170 &theme.widget_blocks.subset(WidgetBlocks::ToggleButton),
171 label.into(),
172 ),
173 data_source,
174 projection: Arc::new(projection),
175 action: EphemeralOpaque::new(Arc::new(action)),
176 })
177 }
178}
179
180impl<D> vui::Layoutable for ToggleButton<D> {
181 fn requirements(&self) -> vui::LayoutRequest {
182 self.common.requirements()
183 }
184}
185
186impl universe::VisitHandles for ActionButton {
187 fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
188 let Self { common, action } = self;
189 common.visit_handles(visitor);
190 action.visit_handles(visitor);
191 }
192}
193impl<D> universe::VisitHandles for ToggleButton<D> {
194 fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
195 let Self {
196 common,
197 action,
198 data_source: _,
199 projection: _,
200 } = self;
201 common.visit_handles(visitor);
202 action.visit_handles(visitor);
203 }
204}
205
206#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Exhaust)]
217#[non_exhaustive]
218pub struct ButtonVisualState {
219 pub(crate) pressed: bool,
222}
223
224impl fmt::Display for ButtonVisualState {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 if self.pressed { "pressed" } else { "idle" }.fmt(f)
228 }
229}
230
231impl universe::VisitHandles for ButtonVisualState {
232 fn visit_handles(&self, _: &mut dyn universe::HandleVisitor) {}
233}
234
235#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Exhaust)]
241#[non_exhaustive]
242pub struct ToggleButtonVisualState {
243 pub(crate) common: ButtonVisualState,
244 pub value: bool,
246}
247
248impl fmt::Display for ToggleButtonVisualState {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 let &Self { value, common } = self;
252 write!(
253 f,
254 "{value}-{common}",
255 value = match value {
256 false => "off",
257 true => "on",
258 },
259 )
260 }
261}
262
263impl ToggleButtonVisualState {
264 pub const fn new(value: bool) -> Self {
266 Self {
267 value,
268 common: ButtonVisualState { pressed: false },
269 }
270 }
271}
272
273impl universe::VisitHandles for ToggleButtonVisualState {
274 fn visit_handles(&self, _: &mut dyn universe::HandleVisitor) {}
275}