use gled_model::asset::{project::Project, scene::grid::GridLocation};
use super::Engine;
use crate::action::UiAction;
#[derive(Debug, Default, Clone, Copy)]
pub(super) struct EffectSelection {
scene: GridLocation,
effect: usize,
}
impl EffectSelection {
pub(super) fn reset(&mut self) {
*self = Self::default();
}
pub(super) fn effect_in(&mut self, scene: GridLocation) -> usize {
if self.scene != scene {
self.scene = scene;
self.effect = 0;
}
self.effect
}
pub(super) fn select(&mut self, scene: GridLocation, effect: usize) {
self.scene = scene;
self.effect = effect;
}
}
impl Engine {
pub(super) fn apply_selection_action(&mut self, action: UiAction) {
let Some(project) = self.project.as_deref() else {
return;
};
let scene = self.selected_scene_instance;
match action {
UiAction::SelectSceneRelative(steps) => {
if let Some(location) = scene_steps_away(project, scene, steps) {
self.selected_scene_instance = location;
}
}
UiAction::SelectSceneEffect(effect) => self.effect_selection.select(scene, effect),
UiAction::SelectSceneEffectRelative(steps) => {
let count = effect_count(project, scene);
let current = self.effect_selection.effect_in(scene);
self.effect_selection
.select(scene, index_steps_away(current, count, steps));
}
_ => {}
}
}
}
fn effect_count(project: &Project, location: GridLocation) -> usize {
project
.scenes_instances_grid
.get(&location)
.map_or(0, |instance| instance.scene.effects().len())
}
fn scene_steps_away(project: &Project, from: GridLocation, steps: i32) -> Option<GridLocation> {
let locations = project.scene_locations_row_major();
let current = match locations.binary_search_by_key(&(from.row, from.col), |location| {
(location.row, location.col)
}) {
Ok(index) => index,
Err(index) => index.saturating_sub(1),
};
locations
.get(index_steps_away(current, locations.len(), steps))
.copied()
}
fn index_steps_away(current: usize, count: usize, steps: i32) -> usize {
let last = count.saturating_sub(1);
let current = current.min(last);
let magnitude = steps.unsigned_abs() as usize;
if steps < 0 {
current.saturating_sub(magnitude)
} else {
current.saturating_add(magnitude).min(last)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn steps_stop_at_the_first_and_last_index() {
assert_eq!(index_steps_away(1, 4, -3), 0);
assert_eq!(index_steps_away(1, 4, 5), 3);
assert_eq!(index_steps_away(1, 4, 1), 2);
}
#[test]
fn a_selection_past_the_end_steps_from_the_last_index() {
assert_eq!(index_steps_away(9, 3, -1), 1);
}
#[test]
fn steps_in_an_empty_list_stay_at_zero() {
assert_eq!(index_steps_away(0, 0, 2), 0);
}
#[test]
fn selecting_another_scene_selects_its_first_effect() {
let mut selection = EffectSelection::default();
selection.select(GridLocation::new(0, 0), 3);
assert_eq!(selection.effect_in(GridLocation::new(0, 0)), 3);
assert_eq!(selection.effect_in(GridLocation::new(1, 0)), 0);
}
}