@ -1,13 +1,17 @@
// TODO:
// * Fix randomly audio not working
// * Lock down system scheduling/run_if conditions
// * Fix tiling repeating
// * Negative coordinates should not mirror
// * UI widget explaining rules/hotkeys
// * Lock down system scheduling/run_if conditions
// * Performance: have a limit on per-note+octave sounds
// * Use a finite set of N audio entities and scale volume instead of
// spawning a bunch of audio entities which is causing issues at >100
// cells
// * Stop creating cells when I didn't mean to (clicking UI, click + dragging)
// * Keep some controls open when main ui panel is closed
// * Exclude power button from web build
// * Show shadow of piece (same tile, just with alpha transparency)
// * Start with an interesting pattern
// * UI minimize widgets
// * Dark mode
// * Make click and drag feel better
// * Add arrow keys to move around
// * "Follow" enhancement: Zoom in/out
#![ allow(clippy::complexity) ]
// Only because of FromTemplat macros unfortunately...
#![ allow(dead_code) ]
@ -37,6 +41,8 @@ fn main() {
. insert_resource ( UiTheme ( create_light_theme ( ) ) )
. init_state ::< SimulationPlayback > ( )
. init_state ::< AudioPlayback > ( )
. init_state ::< DebugUi > ( )
. init_state ::< MainUi > ( )
. add_message ::< Lifecycle > ( )
. add_systems (
Startup ,
@ -48,22 +54,52 @@ fn main() {
load_meshes ,
) ,
)
. configure_sets (
Update ,
( GameSet ::Plan , GameSet ::Apply , GameSet ::React ) . chain ( ) ,
)
// Cell management/Simulation
. add_systems (
Update ,
(
cell_lifecycle . run_if ( on_message ::< Lifecycle > ) ,
update_grid_position . run_if ( on_message ::< CursorMoved > ) ,
de_spawn_cells
. run_if ( not ( is_ui_hovered ) )
. run_if ( not ( is_panning ) )
. run_if ( input_just_released ( MouseButton ::Left ) ) ,
simulation_step
. run_if ( in_state ( SimulationPlayback ::Run ) )
. run_if ( cooldown_secs ( SIM_FRAME_DURATION ) ) ,
simulation_step . run_if ( state_changed ::< SimulationPlayback > ) ,
pause_simulation . run_if ( in_state ( SimulationPlayback ::Step ) ) ,
update_material ,
// Produce lifecycle messages
(
default_board . run_if ( run_once ) ,
update_grid_position . run_if ( on_message ::< CursorMoved > ) ,
de_spawn_cells
. run_if ( not ( is_ui_hovered ) )
. run_if ( not ( is_panning ) )
. run_if ( input_just_released ( MouseButton ::Left ) ) ,
simulation_step . run_if (
in_state ( SimulationPlayback ::Run )
. and_then ( cooldown_secs ( SIM_FRAME_DURATION ) )
. or_else ( state_changed ::< SimulationPlayback > ) ,
) ,
pause_simulation . run_if ( in_state ( SimulationPlayback ::Step ) ) ,
)
. in_set ( GameSet ::Plan ) ,
// Apply lifecycle message
cell_lifecycle
. run_if ( on_message ::< Lifecycle > )
. in_set ( GameSet ::Apply ) ,
// React to board changes
(
reassign_cell_pitch . run_if ( resource_changed ::< PitchMap > ) ,
update_material . run_if (
any_component_added ::< CellPitch >
. or_else ( any_component_changed ::< CellPitch > ) ,
) ,
camera_follow
. run_if ( in_state ( SimulationPlayback ::Run ) )
. run_if ( any_component_changed ::< Coordinates > ) ,
update_camera_position
. run_if ( in_state ( SimulationPlayback ::Run ) )
. run_if ( not ( query_condition_empty ::< (
With < Camera > ,
Changed < Coordinates > ,
) > ) ) ,
)
. in_set ( GameSet ::React ) ,
) ,
)
// Audio Systems
@ -72,28 +108,41 @@ fn main() {
(
control_volume ,
update_pitch_map_resource_ui . run_if ( resource_changed ::< PitchMap > ) ,
reassign_cell_pitch . run_if ( resource_changed ::< PitchMap > ) ,
update_audio ,
update_audio
. run_if (
any_component_added ::< CellPitch >
. or_else ( any_component_changed ::< CellPitch > ) ,
)
. in_set ( GameSet ::React ) ,
) ,
)
// Debugging
. add_systems (
Update ,
(
assert_cell_uniqueness . run_if ( any_with_component ::< Cell > ) ,
grid_gizmo ,
update_debug_info_cell_count ,
update_debug_info_coordinates ,
update_debug_info_active ,
(
assert_cell_uniqueness
. run_if ( any_with_component ::< Cell > )
. after ( update_material )
. after ( update_audio ) ,
update_debug_info_cell_count ,
update_debug_info_active ,
)
. in_set ( GameSet ::React ) ,
) ,
)
// Ui Controllers
. add_systems (
Update ,
(
toggle_state_visible ::< SimulationPlayback > . run_if ( state_changed ::< SimulationPlayback > ) ,
toggle_state_visible ::< SimulationPlayback >
. run_if ( state_changed ::< SimulationPlayback > ) ,
manage_interactive ::< SimulationPlayback > ,
toggle_state_visible ::< AudioPlayback > . run_if ( state_changed ::< AudioPlayback > ) ,
toggle_state_visible ::< DebugUi > . run_if ( state_changed ::< DebugUi > ) ,
toggle_state_visible ::< MainUi > . run_if ( state_changed ::< MainUi > ) ,
manage_interactive ::< AudioPlayback > ,
update_note_interactivity ,
) ,
@ -108,6 +157,13 @@ fn main() {
const SCALE : f32 = 100.0 ;
#[ derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash) ]
enum GameSet {
Plan ,
Apply ,
React ,
}
#[ derive(Component, PartialEq, Clone, Default) ]
enum DebugInfo {
CellCount ,
@ -117,6 +173,20 @@ enum DebugInfo {
Empty ,
}
#[ derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate) ]
enum DebugUi {
#[ default ]
Visible ,
Hidden ,
}
#[ derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate) ]
enum MainUi {
#[ default ]
Visible ,
Hidden ,
}
#[ derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component, FromTemplate) ]
enum AudioPlayback {
#[ default ]
@ -135,11 +205,6 @@ enum UiButton {
ZoomIn ,
PlayAudio ,
MuteAudio ,
AddHighOctave ,
SubHighOctave ,
AddLowOctave ,
SubLowOctave ,
Note ,
Reset ,
Wipe ,
#[ default ]
@ -157,11 +222,6 @@ impl UiButton {
Self ::ZoomIn = > "zoomIn.png" ,
Self ::PlayAudio = > "audioOn.png" ,
Self ::MuteAudio = > "audioOff.png" ,
Self ::AddHighOctave = > "todo.png" ,
Self ::SubHighOctave = > "todo.png" ,
Self ::AddLowOctave = > "todo.png" ,
Self ::SubLowOctave = > "todo.png" ,
Self ::Note = > "todo.png" ,
Self ::Reset = > "return.png" ,
Self ::Wipe = > "trashcan.png" ,
Self ::None = > todo! ( ) ,
@ -199,7 +259,10 @@ fn ui_button(button: UiButton) -> impl Scene {
}
}
fn ui_toggle_button < A : Scene , B : Scene > ( ( button_a , state_a ) : ( UiButton , A ) , ( button_b , state_b ) : ( UiButton , B ) ) -> impl Scene {
fn ui_toggle_button < A : Scene , B : Scene > (
( button_a , state_a ) : ( UiButton , A ) ,
( button_b , state_b ) : ( UiButton , B ) ,
) -> impl Scene {
bsn ! {
@ FeathersButton
Hovered
@ -239,6 +302,7 @@ fn ui_toggle_button<A: Scene, B: Scene>((button_a, state_a): (UiButton, A), (but
fn the_camera ( ) -> impl Scene {
bsn ! {
Camera2d
Coordinates
}
}
@ -288,21 +352,33 @@ fn the_ui() -> impl Scene {
}
fn debug_ui ( ) -> impl Scene {
fn toggle_debug_ui (
_ : On < Pointer < Click > > ,
mut next : ResMut < NextState < DebugUi > > ,
curr : Res < State < DebugUi > > ,
) {
next . set ( match curr . get ( ) {
DebugUi ::Hidden = > DebugUi ::Visible ,
DebugUi ::Visible = > DebugUi ::Hidden ,
} ) ;
}
bsn ! {
Node {
justify_self : JustifySelf ::End ,
display : Display ::Flex ,
flex_direction : FlexDirection ::Column ,
}
// TODO: Toggle visibility when debugging disabled
pane ( )
Transform
Children [
pane_header ( )
on ( toggle_debug_ui )
Children [
Text ( "Debug Info" ) ThemedText ,
] ,
pane_body ( )
DebugUi ::Visible
ToggleStateVisible
Children [
subpane ( )
Node {
@ -386,6 +462,182 @@ fn primary_ui() -> impl Scene {
}
}
fn toggle_main_ui (
_ : On < Pointer < Click > > ,
mut next : ResMut < NextState < MainUi > > ,
curr : Res < State < MainUi > > ,
) {
next . set ( match curr . get ( ) {
MainUi ::Hidden = > MainUi ::Visible ,
MainUi ::Visible = > MainUi ::Hidden ,
} ) ;
}
fn description_text ( ) -> impl Scene {
bsn ! {
subpane ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Column ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
text_setup ( )
Children [
Text ( "Welcome to a little experiment I made: adding sound to Conways Game of Life" ) ThemedText ,
Text ( "---" ) ThemedText ,
Text ( "The buttons above control (in order)" ) ThemedText ,
Text ( "Quit the game" ) ThemedText ,
Text ( "Start/Pause the simulation" ) ThemedText ,
Text ( "Step the simulation" ) ThemedText ,
Text ( "Reset the world state" ) ThemedText ,
Text ( "Wipe the world state" ) ThemedText ,
Text ( "Zoom in/out" ) ThemedText ,
Text ( "Toggle sound on/off" ) ThemedText ,
Text ( "---" ) ThemedText ,
Text ( "Below that are the notes you can toggle on/off" ) ThemedText ,
Text ( "And below that are the number of octaves for each note" ) ThemedText ,
Text ( "Notes are color coded horizontally" ) ThemedText ,
Text ( "Octaves are color coded vertically" ) ThemedText ,
Text ( "---" ) ThemedText ,
Text ( "The camera automatically follows the action" ) ThemedText ,
Text ( "---" ) ThemedText ,
Text ( "Hotkyes" ) ThemedText ,
Text ( "* Space: Play/Pause Simulation" ) ThemedText ,
Text ( "* m: Mute/Play Audio" ) ThemedText ,
Text ( "* r: Reset Simulation" ) ThemedText ,
Text ( "* n: Next Simulation Step" ) ThemedText ,
Text ( "* -/=: Zoom In/Out" ) ThemedText ,
]
}
}
fn controls ( ) -> impl Scene {
bsn ! {
subpane ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
subpane_header ( ) Children [
Text ( "Nav" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
ui_button ( UiButton ::Power )
ActionSource < Action > ( Action ::Quit ) ,
ui_toggle_button ( ( UiButton ::Pause , bsn ! { SimulationPlayback ::Pause } ) , ( UiButton ::Play , bsn ! { SimulationPlayback ::Run } ) )
ActionSource < SimAction > ( SimAction ::Toggle ) ,
ui_button ( UiButton ::Step )
ActionSource < SimAction > ( SimAction ::Step ) ,
ui_button ( UiButton ::Reset )
ActionSource < SimAction > ( SimAction ::Reset ) ,
ui_button ( UiButton ::Wipe )
ActionSource < SimAction > ( SimAction ::Wipe ) ,
ui_button ( UiButton ::ZoomOut )
ActionSource < ZoomAction > ( ZoomAction ::ZoomOut ) ,
ui_button ( UiButton ::ZoomIn )
ActionSource < ZoomAction > ( ZoomAction ::ZoomIn ) ,
ui_toggle_button ( ( UiButton ::MuteAudio , bsn ! { AudioPlayback ::Mute } ) , ( UiButton ::PlayAudio , bsn ! { AudioPlayback ::Play } ) )
ActionSource < AudioAction > ( AudioAction ::Toggle ) ,
]
]
}
}
fn notes ( ) -> impl Scene {
bsn ! {
subpane ( )
Children [
subpane_header ( ) Children [
Text ( "Notes" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
ui_checkbox ( "A" )
template_value ( Note ::A )
on ( toggle_note ) ,
ui_checkbox ( "A#" )
template_value ( Note ::ASharp )
on ( toggle_note ) ,
ui_checkbox ( "B" )
template_value ( Note ::B )
on ( toggle_note ) ,
ui_checkbox ( "C" )
template_value ( Note ::C )
on ( toggle_note ) ,
ui_checkbox ( "C#" )
template_value ( Note ::CSharp )
on ( toggle_note ) ,
ui_checkbox ( "D" )
template_value ( Note ::D )
on ( toggle_note ) ,
ui_checkbox ( "D#" )
template_value ( Note ::DSharp )
on ( toggle_note ) ,
ui_checkbox ( "E" )
template_value ( Note ::E )
on ( toggle_note ) ,
ui_checkbox ( "F" )
template_value ( Note ::F )
on ( toggle_note ) ,
ui_checkbox ( "F#" )
template_value ( Note ::FSharp )
on ( toggle_note ) ,
ui_checkbox ( "G" )
template_value ( Note ::G )
on ( toggle_note ) ,
ui_checkbox ( "G#" )
template_value ( Note ::GSharp )
on ( toggle_note ) ,
]
]
}
}
fn octaves ( ) -> impl Scene {
bsn ! {
subpane ( )
subpane_header ( ) Children [
Text ( "Octaves" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
@ FeathersSlider { @ max : 11. , @ value : 5. , @ min : 1. }
SliderStep ( 2. )
SliderPrecision ( 0 )
on ( | value_change : On < ValueChange < f32 > > , mut pitch_map : ResMut < PitchMap > | {
if value_change . is_final {
pitch_map . octaves = value_change . value as usize ;
}
} )
on ( slider_self_update ) ,
]
}
}
bsn ! {
Node {
display : Display ::Flex ,
@ -396,133 +648,19 @@ fn primary_ui() -> impl Scene {
pane ( )
Children [
pane_header ( )
on ( toggle_main_ui )
// todo: toggle main ui
Children [
Text ( "Conways Game of Life With Sound" ) ThemedText ,
Text ( "Conways Game of Life With Sound (click here to open control panel) ") ThemedText ,
] ,
pane_body ( )
MainUi ::Visible
ToggleStateVisible
Children [
subpane ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
subpane_header ( ) Children [
Text ( "Nav" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
ui_button ( UiButton ::Power )
ActionSource < Action > ( Action ::Quit ) ,
ui_toggle_button ( ( UiButton ::Pause , bsn ! { SimulationPlayback ::Pause } ) , ( UiButton ::Play , bsn ! { SimulationPlayback ::Run } ) )
ActionSource < SimAction > ( SimAction ::Toggle ) ,
ui_button ( UiButton ::Step )
ActionSource < SimAction > ( SimAction ::Step ) ,
ui_button ( UiButton ::Reset )
ActionSource < SimAction > ( SimAction ::Reset ) ,
ui_button ( UiButton ::Wipe )
ActionSource < SimAction > ( SimAction ::Wipe ) ,
ui_button ( UiButton ::ZoomOut )
ActionSource < ZoomAction > ( ZoomAction ::ZoomOut ) ,
ui_button ( UiButton ::ZoomIn )
ActionSource < ZoomAction > ( ZoomAction ::ZoomIn ) ,
ui_toggle_button ( ( UiButton ::MuteAudio , bsn ! { AudioPlayback ::Mute } ) , ( UiButton ::PlayAudio , bsn ! { AudioPlayback ::Play } ) )
ActionSource < AudioAction > ( AudioAction ::Toggle ) ,
]
] ,
subpane ( )
Children [
subpane_header ( ) Children [
Text ( "Notes" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
ui_checkbox ( "A" )
template_value ( Note ::A )
on ( toggle_note ) ,
ui_checkbox ( "A#" )
template_value ( Note ::ASharp )
on ( toggle_note ) ,
ui_checkbox ( "B" )
template_value ( Note ::B )
on ( toggle_note ) ,
ui_checkbox ( "C" )
template_value ( Note ::C )
on ( toggle_note ) ,
ui_checkbox ( "C#" )
template_value ( Note ::CSharp )
on ( toggle_note ) ,
ui_checkbox ( "D" )
template_value ( Note ::D )
on ( toggle_note ) ,
ui_checkbox ( "D#" )
template_value ( Note ::DSharp )
on ( toggle_note ) ,
ui_checkbox ( "E" )
template_value ( Note ::E )
on ( toggle_note ) ,
ui_checkbox ( "F" )
template_value ( Note ::F )
on ( toggle_note ) ,
ui_checkbox ( "F#" )
template_value ( Note ::FSharp )
on ( toggle_note ) ,
ui_checkbox ( "G" )
template_value ( Note ::G )
on ( toggle_note ) ,
ui_checkbox ( "G#" )
template_value ( Note ::GSharp )
on ( toggle_note ) ,
]
] ,
subpane ( )
subpane_header ( ) Children [
Text ( "Octaves" ) ThemedText
]
subpane_body ( )
Node {
display : Display ::Flex ,
flex_direction : FlexDirection ::Row ,
align_items : AlignItems ::Stretch ,
justify_content : JustifyContent ::Start ,
}
Children [
@ FeathersSlider { @ max : 5. , @ value : 3. , @ min : 1. }
SliderStep ( 1. )
SliderPrecision ( 0 )
on ( | value_change : On < ValueChange < f32 > > , mut pitch_map : ResMut < PitchMap > | {
if value_change . is_final {
pitch_map . octaves_up = value_change . value as usize ;
debug ! ( "(octave up) Updated pitch map: {:#?}" , pitch_map ) ;
}
} )
on ( slider_self_update ) ,
@ FeathersSlider { @ max : 5. , @ value : 3. , @ min : 1. }
SliderStep ( 1. )
SliderPrecision ( 0 )
on ( | value_change : On < ValueChange < f32 > > , mut pitch_map : ResMut < PitchMap > | {
if value_change . is_final {
pitch_map . octaves_down = value_change . value as usize ;
debug ! ( "(octave down) Updated pitch map: {:#?}" , pitch_map ) ;
}
} )
on ( slider_self_update ) ,
] ,
controls ( ) ,
notes ( ) ,
octaves ( ) ,
description_text ( ) ,
]
]
}
@ -587,39 +725,19 @@ impl Coordinates {
}
/// For the purposes of creating assests, what does the given coordinate translate to CellPitch wise
fn to_cell_pitch ( & self ) -> CellPitch {
let Coordinates { x , y } = self . normalized ( ) ;
let note = match x {
0 = > Note ::A ,
1 = > Note ::ASharp ,
2 = > Note ::B ,
3 = > Note ::C ,
4 = > Note ::CSharp ,
5 = > Note ::D ,
6 = > Note ::DSharp ,
7 = > Note ::E ,
8 = > Note ::F ,
9 = > Note ::FSharp ,
10 = > Note ::G ,
11 = > Note ::GSharp ,
_ = > panic! ( "This shouldn't happen!" ) ,
} ;
let octave = y ;
CellPitch { note , octave }
}
fn to_cell_pitch ( & self , pm : & PitchMap ) -> CellPitch {
let rows = pm . notes . len ( ) ;
let cols = pm . octaves ;
// Turn an arbitrary coordinate to a "normalized" coordinate centered on 0,0
fn normalized ( & self ) -> Self {
println! ( "normalizing {:?}" , ( self . x , self . y ) ) ;
let x = self . x . rem_euclid ( 12 ) ;
let y = self . y . rem_euclid ( 11 ) ;
println! ( "normalized {:?}" , ( x , y ) ) ;
debug_assert! ( x > = 0 ) ;
debug_assert! ( x < = 12 ) ;
debug_assert! ( y > = 0 ) ;
debug_assert! ( y < = 11 ) ;
let this_x = self . x . rem_euclid ( rows as isize ) ;
let this_y = self . y . rem_euclid ( cols as isize ) - ( pm . octaves as isize / 2 ) ;
Coordinates { x , y }
debug_assert! ( this_x > = 0 ) ;
let note = pm . notes . get ( this_x as usize ) . unwrap ( ) . clone ( ) ;
let octave = this_y ;
CellPitch { note , octave }
}
}
@ -798,24 +916,21 @@ fn new_cell(
let playback_settings = PlaybackSettings ::ONCE . with_volume ( volume ) ;
let cell_pitch = pitch_map. coordinates_cell_pitch ( & c ) ;
let cell_pitch = c. to_cell_pitch ( & ( * pitch_map ) ) ;
#[ derive(Clone, Default, Component) ]
enum Foo {
#[ default ]
A ,
}
debug ! ( "cell pitch: {:#?}" , cell_pitch ) ;
debug ! ( "materials: {:#?}" , cell_assets . materials ) ;
bsn ! {
Cell
Coordinates { x , y }
template_value ( cell_pitch )
template_value ( cell_pitch .clone ( ) )
template_value ( playback_settings )
template_value ( Transform ::from_translation ( c . as_translation ( ) + Vec3 ::new ( 0.0 , 0.0 , 1.0 ) ) )
template_value ( Mesh2dTemplate ( cell_assets . mesh . clone ( ) . into ( ) ) )
// AudioPlayer added by update_audio
// MeshMaterial2d added by update_material
template_value ( MeshMaterial2dTemplate ( cell_assets . materials . get ( & pitch_map. coordinates_ cell_pitch( & c ) ) . unwrap ( ) . clone ( ) . into ( ) ) )
template_value ( MeshMaterial2dTemplate ( cell_assets . materials . get ( & cell_pitch) . unwrap ( ) . clone ( ) . into ( ) ) )
}
}
@ -981,7 +1096,7 @@ impl CellPitch {
]
. into_iter ( )
. flat_map ( | note | {
( 0 .. 11 ) . map ( move | octave | CellPitch {
( - 5 ..= 5 ) . map ( move | octave | CellPitch {
note : note . clone ( ) ,
octave ,
} )
@ -1060,6 +1175,15 @@ fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Me
cell_assets . mesh = meshes . add ( Rectangle ::new ( SCALE , SCALE ) ) ;
}
fn default_board ( mut writer : MessageWriter < Lifecycle > ) {
// https://playgameoflife.com/lexicon/R-pentomino
[ ( 0 , 0 ) , ( 0 , - 1 ) , ( - 1 , 0 ) , ( 0 , 1 ) , ( 1 , 1 ) ]
. into_iter ( )
. for_each ( | ( x , y ) | {
writer . write ( Lifecycle ::Alive ( Coordinates { x , y } ) ) ;
} ) ;
}
// TODO: Do not place piece if panning
// TODO: Tie cursor to real world space, not pixels
fn mouse_pan (
@ -1143,16 +1267,14 @@ fn manage_interactive<T: Component + States + PartialEq>(
#[ derive(Resource, Debug) ]
struct PitchMap {
notes : Vec < Note > ,
octaves_up : usize ,
octaves_down : usize ,
octaves : usize ,
}
impl Default for PitchMap {
fn default ( ) -> Self {
Self {
notes : vec ! [ Note ::A , Note ::B , Note ::CSharp , Note ::E , Note ::FSharp ] ,
octaves_up : 3 ,
octaves_down : 3 ,
octaves : 5 ,
}
}
}
@ -1167,72 +1289,33 @@ impl PitchMap {
// always ensure the notes list is sorted
self . notes . sort ( ) ;
}
/// Given the currently selected notes and octave range selected in the PitchMap convert the given coordinates to a CellPitch
fn coordinates_cell_pitch ( & self , c : & Coordinates ) -> CellPitch {
let Coordinates { x , y } = c . normalized ( ) ;
let note : Note = {
let num_notes = self . notes . len ( ) ;
self . notes . get ( ( x % num_notes as isize ) as usize ) . unwrap ( ) . clone ( )
} ;
let octave : isize = {
let num_octaves = self . octaves_up + self . octaves_down + 1 ;
println! ( "num_octaves: {:?}" , num_octaves ) ;
( y % num_octaves as isize ) as isize
} ;
let cp = CellPitch { note , octave } ;
println! ( "Cell Pitch: {:?}" , cp ) ;
cp
}
}
#[ test ]
fn test_cell_pitch ( ) {
let pm = PitchMap {
notes : vec ! [ Note ::A , Note ::B , Note ::C ] ,
octaves_up : 1 ,
octaves_down : 1 ,
octaves : 3 ,
} ;
{
let xs = [ - 6 , - 3 , 0 , 3 , 6 ] ;
let ys = [ - 5 , - 2 , 0 , 3 , 6 ] ;
xs . iter ( ) . for_each ( | x | {
ys . iter ( ) . for_each ( | y | {
let CellPitch { note , octave } = pm . coordinates_cell_pitch ( & Coordinates { x : * x , y : * y } ) ;
debug_assert_eq! ( note , Note ::A ) ;
debug_assert_eq! ( octave , 0 ) ;
} ) ;
} ) ;
}
{
let xs = [ - 5 , - 2 , 1 , 4 , 7 ] ;
let ys = [ - 4 , - 1 , 1 , 4 , 7 ] ;
xs . iter ( ) . for_each ( | x | {
ys . iter ( ) . for_each ( | y | {
let CellPitch { note , octave } = pm . coordinates_cell_pitch ( & Coordinates { x : * x , y : * y } ) ;
debug_assert_eq! ( note , Note ::B ) ;
debug_assert_eq! ( octave , 1 ) ;
} ) ;
} ) ;
}
{
let xs = [ - 4 , - 1 , 2 , 5 , 8 ] ;
let ys = [ - 3 , 0 , 2 , 5 , 8 ] ;
xs . iter ( ) . for_each ( | x | {
ys . iter ( ) . for_each ( | y | {
let CellPitch { note , octave } = pm . coordinates_cell_pitch ( & Coordinates { x : * x , y : * y } ) ;
debug_assert_eq! ( note , Note ::C ) ;
debug_assert_eq! ( octave , 2 ) ;
} ) ;
} ) ;
}
let xs = - 6 ..= 5 ;
let ys = - 6 ..= 4 ;
xs . enumerate ( ) . for_each ( | ( i , x ) | {
ys . clone ( ) . enumerate ( ) . for_each ( | ( j , y ) | {
let c = Coordinates { x , y } ;
let n = pm . notes . get ( i % 3 ) . unwrap ( ) ;
let o = ( j as isize % 3 ) - 1 ;
let cp = c . to_cell_pitch ( & pm ) ;
debug_assert_eq! ( cp . note , * n ) ;
debug_assert_eq! ( cp . octave , o ) ;
} )
} ) ;
}
/// When the pitch map changes, update all cells to use the correct pitch (note + octave)
fn reassign_cell_pitch ( mut query : Query < ( & mut CellPitch , & Coordinates ) > , pitch_map : Res < PitchMap > ) {
debug ! ( "query size: {:?}" , query . iter ( ) . len ( ) ) ;
query . iter_mut ( ) . for_each ( | ( mut cell_pitch , c ) | {
* cell_pitch = pitch_map. coordinates_cell_pitch ( c ) ;
* cell_pitch = c . to_cell_pitch ( & ( * pitch_map ) ) ;
debug ! ( "Updating cell pitch for {:?}" , c ) ;
} ) ;
}
@ -1321,7 +1404,7 @@ struct ToggleStateVisible;
fn toggle_state_visible < S : States + Component > (
mut q : Query < ( & S , & mut Node ) , With < ToggleStateVisible > > ,
c : Res < State < S > >
c : Res < State < S > > ,
) {
q . iter_mut ( ) . for_each ( | ( s , mut node ) | {
node . display = if s = = c . get ( ) {
@ -1331,3 +1414,41 @@ fn toggle_state_visible<S: States + Component>(
} ;
} )
}
fn camera_follow (
mut camera : Query < & mut Coordinates , With < Camera > > ,
cells : Query < & Coordinates , ( With < Cell > , Without < Camera > ) > ,
) {
camera . iter_mut ( ) . for_each ( | mut c | {
let left = cells
. iter ( )
. map ( | Coordinates { x , .. } | * x )
. min ( )
. unwrap_or ( 0 ) ;
let right = cells
. iter ( )
. map ( | Coordinates { x , .. } | * x )
. max ( )
. unwrap_or ( 0 ) ;
let top = cells
. iter ( )
. map ( | Coordinates { y , .. } | * y )
. max ( )
. unwrap_or ( 0 ) ;
let bottom = cells
. iter ( )
. map ( | Coordinates { y , .. } | * y )
. min ( )
. unwrap_or ( 0 ) ;
c . x = ( right + left ) / 2 ;
c . y = ( top + bottom ) / 2 ;
} ) ;
}
fn update_camera_position ( mut query : Query < ( & Coordinates , & mut Transform ) , With < Camera > > ) {
query . iter_mut ( ) . for_each ( | ( c , mut t ) | {
t . translation = c . as_translation ( ) ;
} ) ;
}