1mod utils;
3use utils::*;
4
5extern crate alloc;
6use alloc::collections::BTreeMap;
7
8use bevy_platform::collections::HashMap;
9
10use bevy::{prelude::*, window::WindowResolution};
11use jonmo::prelude::*;
12
13const SAVE_CHARS: &str = "abcdefgh";
14
15fn main() {
16 let mut app = App::new();
17
18 let save_states: HashMap<_, _> = SAVE_CHARS
19 .chars()
20 .map(|save_char| {
21 (
22 save_char,
23 MutableBTreeMap::builder()
24 .values(
25 ROWS.iter()
26 .flat_map(|row| row.chars().map(|letter| (letter, LetterData::default())))
27 .collect::<BTreeMap<_, _>>(),
28 )
29 .spawn(app.world_mut()),
30 )
31 })
32 .collect();
33
34 app.add_plugins(examples_plugin)
35 .insert_resource(SaveStates(save_states))
36 .insert_resource(ActiveSave('a'))
37 .add_systems(
38 Startup,
39 (
40 |world: &mut World| {
41 ui_root().spawn(world);
42 },
43 camera,
44 ),
45 )
46 .add_systems(Update, listen)
47 .run();
48}
49
50const ROWS: [&str; 3] = ["qwertyuiop", "asdfghjkl", "zxcvbnm"];
51
52#[derive(Default, Clone, Debug)]
53struct LetterData {
54 count: usize,
55 pressed: bool,
56}
57
58#[derive(Resource, Clone)]
59struct SaveStates(HashMap<char, MutableBTreeMap<char, LetterData>>);
60
61#[derive(Resource, Clone, Copy, PartialEq)]
62struct ActiveSave(char);
63
64fn get_active_map(save_states: &SaveStates, active_save: ActiveSave) -> MutableBTreeMap<char, LetterData> {
65 save_states.0.get(&active_save.0).unwrap().clone()
66}
67
68const LETTER_SIZE: f32 = 60.;
69
70fn ui_root() -> jonmo::Builder {
71 let active_save = signal::from_resource_changed::<ActiveSave>();
72
73 jonmo::Builder::from(Node {
74 height: Val::Percent(100.0),
75 width: Val::Percent(100.0),
76 justify_content: JustifyContent::Center,
77 ..default()
78 })
79 .child(
80 jonmo::Builder::from(Node {
81 flex_direction: FlexDirection::Column,
82 row_gap: Val::Px(GAP * 2.),
83 padding: UiRect::all(Val::Px(GAP * 4.)),
84 width: Val::Px(WindowResolution::default().physical_width() as f32),
85 justify_content: JustifyContent::Center,
86 ..default()
87 })
88 .child(
89 jonmo::Builder::from(Node {
90 align_self: AlignSelf::Center,
91 width: Val::Percent(100.),
92 ..default()
93 })
94 .child(
95 jonmo::Builder::from(Node {
96 flex_direction: FlexDirection::Row,
97 column_gap: Val::Px(GAP * 2.),
98 ..default()
99 })
100 .children(SAVE_CHARS.chars().map(clone!((active_save) move |save_char| {
101 save_card(save_char, active_save.clone())
102 }))),
103 )
104 .child(
105 sum_container()
106 .child(
107 text_node()
108 .insert((TextColor(BLUE), TextFont::from_font_size(LETTER_SIZE)))
109 .with_component::<Node>(|mut node| node.height = Val::Px(100.))
110 .component_signal(
111 active_save
112 .clone()
113 .switch_signal_vec(move |In(active_save): In<ActiveSave>, save_states: Res<SaveStates>| {
114 get_active_map(&save_states, active_save).signal_vec_entries()
115 })
116 .map_in(|(_, LetterData { count, .. })| count)
117 .sum()
118 .dedupe()
119 .map_in_ref(ToString::to_string)
120 .map_in(Text)
121 .map_in(Some),
122 )
123 ),
124 ),
125 )
126 .children(ROWS.into_iter().map(clone!((active_save) move |row| {
127 jonmo::Builder::from(Node {
128 flex_direction: FlexDirection::Row,
129 column_gap: Val::Px(GAP * 2.),
130 ..default()
131 })
132 .children(row.chars().map(
133 clone!((active_save) move |l| {
134 let letter_data = active_save
135 .clone()
136 .switch_signal_map(move |In(active_save): In<ActiveSave>, save_states: Res<SaveStates>| {
137 get_active_map(&save_states, active_save).signal_map()
138 })
139 .key(l)
140 .map_in(Option::unwrap_or_default);
141 letter(l, letter_data)
142 }),
143 ))
144 .child(
145 sum_container()
146 .child(
147 text_node()
148 .insert((TextColor(BLUE), TextFont::from_font_size(LETTER_SIZE)))
149 .component_signal(
150 active_save
151 .clone()
152 .switch_signal_vec(move |In(active_save): In<ActiveSave>, save_states: Res<SaveStates>| {
153 get_active_map(&save_states, active_save).signal_vec_entries()
154 })
155 .filter(move |In((letter, _))| row.contains(letter))
156 .map_in(|(_, LetterData { count, .. })| count)
157 .sum()
158 .dedupe()
159 .map_in_ref(ToString::to_string)
160 .map_in(Text)
161 .map_in(Some),
162 ),
163 ),
164 )
165 }))),
166 )
167}
168
169const GAP: f32 = 5.;
170
171fn sum_container() -> jonmo::Builder {
172 jonmo::Builder::from(Node {
173 align_self: AlignSelf::Center,
174 justify_content: JustifyContent::FlexEnd,
175 flex_grow: 1.,
176 padding: UiRect::all(Val::Px(GAP * 2.)),
177 ..default()
178 })
179}
180
181fn save_card(save_char: char, active_save_signal: impl Signal<Item = ActiveSave> + Clone) -> jonmo::Builder {
182 jonmo::Builder::from((Node {
183 width: Val::Px(100.),
184 height: Val::Px(100.),
185 justify_content: JustifyContent::Center,
186 align_items: AlignItems::Center,
187 padding: UiRect::all(Val::Px(GAP * 2.)),
188 border_radius: BorderRadius::all(Val::Px(GAP * 2.)),
189 ..default()
190 },))
191 .observe(move |_click: On<Pointer<Click>>, mut active_save: ResMut<ActiveSave>| {
192 active_save.0 = save_char;
193 })
194 .component_signal(
195 active_save_signal
196 .map_in(move |ActiveSave(active_char)| {
197 if active_char == save_char {
198 BackgroundColor(BLUE)
199 } else {
200 BackgroundColor(PINK)
201 }
202 })
203 .map_in(Some),
204 )
205 .child(text_node().insert((
206 Text(save_char.to_string()),
207 TextColor(Color::WHITE),
208 TextFont::from_font_size(LETTER_SIZE),
209 )))
210}
211
212fn text_node() -> jonmo::Builder {
213 jonmo::Builder::from((
214 Node {
215 border_radius: BorderRadius::all(Val::Px(GAP)),
216 ..default()
217 },
218 TextColor(Color::WHITE),
219 TextLayout::new_with_justify(Justify::Center),
220 ))
221}
222
223fn letter(letter: char, data: impl Signal<Item = LetterData> + Clone) -> jonmo::Builder {
224 jonmo::Builder::from((Node {
225 flex_direction: FlexDirection::Column,
226 row_gap: Val::Px(GAP * 2.),
227 padding: UiRect::all(Val::Px(GAP * 2.)),
228 width: Val::Px(100.),
229 border_radius: BorderRadius::all(Val::Px(GAP * 2.)),
230 ..default()
231 },))
232 .component_signal(
233 data.clone()
234 .map_in(|LetterData { pressed, .. }| pressed)
235 .dedupe()
236 .map_true_in(|| Outline {
237 width: Val::Px(1.),
238 ..default()
239 }),
240 )
241 .child(text_node().insert((
242 Text(letter.to_string()),
243 TextColor(PINK),
244 TextFont::from_font_size(LETTER_SIZE),
245 )))
246 .child(
247 text_node()
248 .insert(TextFont::from_font_size(LETTER_SIZE / 1.5))
249 .component_signal(
250 data.clone()
251 .map_in(|LetterData { count, .. }| count)
252 .dedupe()
253 .map_in_ref(ToString::to_string)
254 .map_in(Text)
255 .map_in(Some),
256 ),
257 )
258}
259
260fn listen(
261 keys: ResMut<ButtonInput<KeyCode>>,
262 save_states: Res<SaveStates>,
263 active_save: Res<ActiveSave>,
264 mut mutable_btree_map_datas: Query<&mut MutableBTreeMapData<char, LetterData>>,
265) {
266 let current_map = get_active_map(&save_states, *active_save);
267 let map = HashMap::from([
268 (KeyCode::KeyA, 'a'),
269 (KeyCode::KeyB, 'b'),
270 (KeyCode::KeyC, 'c'),
271 (KeyCode::KeyD, 'd'),
272 (KeyCode::KeyE, 'e'),
273 (KeyCode::KeyF, 'f'),
274 (KeyCode::KeyG, 'g'),
275 (KeyCode::KeyH, 'h'),
276 (KeyCode::KeyI, 'i'),
277 (KeyCode::KeyJ, 'j'),
278 (KeyCode::KeyK, 'k'),
279 (KeyCode::KeyL, 'l'),
280 (KeyCode::KeyM, 'm'),
281 (KeyCode::KeyN, 'n'),
282 (KeyCode::KeyO, 'o'),
283 (KeyCode::KeyP, 'p'),
284 (KeyCode::KeyQ, 'q'),
285 (KeyCode::KeyR, 'r'),
286 (KeyCode::KeyS, 's'),
287 (KeyCode::KeyT, 't'),
288 (KeyCode::KeyU, 'u'),
289 (KeyCode::KeyV, 'v'),
290 (KeyCode::KeyW, 'w'),
291 (KeyCode::KeyX, 'x'),
292 (KeyCode::KeyY, 'y'),
293 (KeyCode::KeyZ, 'z'),
294 ]);
295 for (key, char) in map.iter() {
296 if keys.just_pressed(*key) {
297 let mut guard = current_map.write(&mut mutable_btree_map_datas);
298 guard.insert(
299 *char,
300 LetterData {
301 pressed: true,
302 count: guard.get(char).unwrap().count + 1,
303 },
304 );
305 } else if keys.just_released(*key) {
306 let mut guard = current_map.write(&mut mutable_btree_map_datas);
307 guard.insert(
308 *char,
309 LetterData {
310 pressed: false,
311 ..guard.get(char).unwrap().clone()
312 },
313 );
314 }
315 }
316}
317
318fn camera(mut commands: Commands) {
319 commands.spawn(Camera2d);
320}