scroll_grid/
scroll_grid.rs

1//! Grid of letters that can be scrolled vertically or horizontally.
2//!
3//! i can't believe it's not scrolling !
4
5mod 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; // 65 / 1.2
60
61#[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}