scroll_grid/
scroll_grid.rs1mod utils;
6use utils::*;
7
8use bevy::{input::mouse::MouseWheel, prelude::*};
9use haalka::prelude::*;
10
11fn main() {
12 let letters = "abcdefghijklmnopqrstuvwxyz";
13 let vertical = (0..5)
14 .map(|i| {
15 letters
16 .chars()
17 .cycle()
18 .skip(i)
19 .take(26)
20 .enumerate()
21 .map(|(j, letter)| LetterColor {
22 letter: letter.to_string(),
23 color: ROYGBIV[j % 7].into(),
24 })
25 .collect::<Vec<_>>()
26 })
27 .collect::<Vec<_>>();
28 let horizontal = (0..5)
29 .map(|i| {
30 letters
31 .chars()
32 .cycle()
33 .skip(i)
34 .take(26)
35 .map(|letter| LetterColor {
36 letter: letter.to_string(),
37 color: ROYGBIV[i].into(),
38 })
39 .collect::<Vec<_>>()
40 })
41 .collect::<Vec<_>>();
42 App::new()
43 .add_plugins(examples_plugin)
44 .add_systems(
45 Startup,
46 (
47 |world: &mut World| {
48 ui_root().spawn(world);
49 },
50 camera,
51 ),
52 )
53 .add_systems(Update, (scroller.run_if(resource_exists::<HoveredCell>), shifter))
54 .insert_resource(Rails { vertical, horizontal })
55 .insert_resource(Shifted(false))
56 .run();
57}
58
59const LETTER_SIZE: f32 = 54.167; #[derive(Clone, Copy)]
62enum Scroll {
63 Up,
64 Down,
65}
66
67#[derive(Resource)]
68struct HoveredCell(usize, usize);
69
70#[rustfmt::skip]
71fn letter(
72 x: usize,
73 y: usize,
74 letter_color: impl Signal<Item = LetterColor> + Send + Sync + 'static,
75) -> impl Element {
76 let letter_color = letter_color.broadcast();
77 let letter = letter_color.signal_ref(|LetterColor { letter, .. }| letter.clone());
78 let color = letter_color.signal_ref(|LetterColor { color, .. }| *color);
79 El::<Text>::new()
80 .on_hovered_change(move |is_hovered| {
81 if is_hovered {
82 async_world().insert_resource(HoveredCell(x, y)).apply(spawn).detach()
83 }
84 })
85 .text_font(TextFont::from_font_size(LETTER_SIZE))
86 .text_color_signal(color.map(TextColor))
87 .text_signal(letter.map(Text))
88}
89
90#[derive(Clone, Default)]
91struct LetterColor {
92 letter: String,
93 color: Color,
94}
95
96#[derive(Resource)]
97struct Rails {
98 vertical: Vec<Vec<LetterColor>>,
99 horizontal: Vec<Vec<LetterColor>>,
100}
101
102const ROYGBIV: &[Srgba] = &[
103 bevy::color::palettes::css::RED,
104 bevy::color::palettes::css::ORANGE,
105 bevy::color::palettes::css::YELLOW,
106 bevy::color::palettes::css::GREEN,
107 bevy::color::palettes::css::BLUE,
108 bevy::color::palettes::css::INDIGO,
109 bevy::color::palettes::css::VIOLET,
110];
111
112static CELLS: LazyLock<Vec<Vec<Mutable<LetterColor>>>> = LazyLock::new(|| {
113 let cells = (0..5)
114 .map(|_| (0..5).map(|_| Mutable::new(default())).collect::<Vec<_>>())
115 .collect::<Vec<_>>();
116 let letters = "abcdefghijklmnopqrstuvwxyz";
117 for i in 0..5 {
118 for (j, letter) in letters.chars().skip(i).take(5).enumerate() {
119 cells[i][j].set(LetterColor {
120 letter: letter.to_string(),
121 color: ROYGBIV[i].into(),
122 });
123 }
124 }
125 cells
126});
127
128fn ui_root() -> impl Element {
129 El::<Node>::new()
130 .with_node(|mut node| {
131 node.width = Val::Percent(100.);
132 node.height = Val::Percent(100.);
133 })
134 .align_content(Align::center())
135 .child(
136 Grid::<Node>::new()
137 .on_hovered_change(move |is_hovered| {
138 if !is_hovered {
139 async_world().remove_resource::<HoveredCell>().apply(spawn).detach()
140 }
141 })
142 .row_wrap_cell_width(48.)
143 .with_node(|mut node| {
144 node.width = Val::Px(300.);
145 node.height = Val::Px(5. * LETTER_SIZE);
146 node.column_gap = Val::Px(15.);
147 })
148 .align(Align::center())
149 .cells(CELLS.iter().enumerate().flat_map(|(x, cells)| {
150 cells
151 .iter()
152 .enumerate()
153 .map(move |(y, cell)| letter(x, y, cell.signal_cloned()))
154 })),
155 )
156}
157
158fn scroller(
159 mut mouse_wheel_events: EventReader<MouseWheel>,
160 hovered_cell: Res<HoveredCell>,
161 mut rails: ResMut<Rails>,
162 shifted: Res<Shifted>,
163) {
164 for mouse_wheel_event in mouse_wheel_events.read() {
165 let scroll = if mouse_wheel_event.y.is_sign_negative() {
166 Scroll::Up
167 } else {
168 Scroll::Down
169 };
170 let HoveredCell(x, y) = *hovered_cell;
171 let Rails { vertical, horizontal } = &mut *rails;
172 match scroll {
173 Scroll::Up => {
174 if shifted.0 {
175 horizontal[x].rotate_left(1);
176 for (v, h) in vertical.iter_mut().zip(horizontal[x].iter()) {
177 v[x] = h.clone();
178 }
179 for (cell, v) in CELLS[x].iter().zip(horizontal[x].iter()) {
180 cell.set(v.clone());
181 }
182 } else {
183 vertical[y].rotate_left(1);
184 for (h, v) in horizontal.iter_mut().zip(vertical[y].iter()) {
185 h[y] = v.clone();
186 }
187 for (cell, v) in CELLS.iter().zip(vertical[y].iter()) {
188 cell[y].set(v.clone());
189 }
190 }
191 }
192 Scroll::Down => {
193 if shifted.0 {
194 horizontal[x].rotate_right(1);
195 for (v, h) in vertical.iter_mut().zip(horizontal[x].iter()) {
196 v[x] = h.clone();
197 }
198 for (cell, v) in CELLS[x].iter().zip(horizontal[x].iter()) {
199 cell.set(v.clone());
200 }
201 } else {
202 vertical[y].rotate_right(1);
203 for (h, v) in horizontal.iter_mut().zip(vertical[y].iter()) {
204 h[y] = v.clone();
205 }
206 for (cell, v) in CELLS.iter().zip(vertical[y].iter()) {
207 cell[y].set(v.clone());
208 }
209 }
210 }
211 }
212 }
213}
214
215#[derive(Resource)]
216struct Shifted(bool);
217
218fn shifter(keys: Res<ButtonInput<KeyCode>>, mut shifted: ResMut<Shifted>) {
219 if keys.just_pressed(KeyCode::ShiftLeft) || keys.just_pressed(KeyCode::ShiftRight) {
220 shifted.0 = true;
221 } else if keys.just_released(KeyCode::ShiftLeft) || keys.just_released(KeyCode::ShiftRight) {
222 shifted.0 = false;
223 }
224}
225
226fn camera(mut commands: Commands) {
227 commands.spawn(Camera2d);
228}