parent
8700c23893
commit
f15421a461
6 changed files with 170 additions and 12 deletions
@ -0,0 +1,150 @@ |
|||||||
|
use bevy::color::palettes::css::*; |
||||||
|
use bevy::prelude::*; |
||||||
|
use common::prelude::*; |
||||||
|
|
||||||
|
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||||
|
|
||||||
|
use crate::assets::block_visuals::BlockVisuals; |
||||||
|
|
||||||
|
const SCALE: i32 = 4; |
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) { |
||||||
|
let blocks_changed = Manifest::<BlockVisuals>::changed; |
||||||
|
app.add_systems(PostUpdate, setup_selection_ui.run_if(blocks_changed)); |
||||||
|
app.add_systems(Update, update_selection_visibility); |
||||||
|
let selection_changed = resource_exists_and_changed::<SelectedBlock>; |
||||||
|
app.add_systems(Update, update_selected_cell.run_if(selection_changed)); |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut)] |
||||||
|
pub struct SelectedBlock(Identifier<Block>); |
||||||
|
|
||||||
|
#[derive(Component)] |
||||||
|
struct BlockSelectionLayout; |
||||||
|
|
||||||
|
#[derive(Component)] |
||||||
|
#[require(Button, Pickable)] |
||||||
|
struct BlockSelectionCell { |
||||||
|
id: Identifier<Block>, |
||||||
|
} |
||||||
|
|
||||||
|
fn setup_selection_ui( |
||||||
|
existing: Option<Single<Entity, With<BlockSelectionLayout>>>, |
||||||
|
blocks: Manifest<BlockVisuals>, |
||||||
|
mut commands: Commands, |
||||||
|
) { |
||||||
|
// Despawn previous block selection UI, if present.
|
||||||
|
if let Some(existing) = existing { |
||||||
|
commands.entity(*existing).despawn(); |
||||||
|
} |
||||||
|
|
||||||
|
commands |
||||||
|
.spawn(( |
||||||
|
BlockSelectionLayout, |
||||||
|
Node { |
||||||
|
width: percent(100), |
||||||
|
height: percent(100), |
||||||
|
align_items: AlignItems::Center, |
||||||
|
justify_content: JustifyContent::Center, |
||||||
|
..default() |
||||||
|
}, |
||||||
|
)) |
||||||
|
.with_children(|builder| { |
||||||
|
builder |
||||||
|
.spawn(( |
||||||
|
Pickable::default(), |
||||||
|
Node { |
||||||
|
display: Display::Grid, |
||||||
|
padding: UiRect::all(px(4 * SCALE)), |
||||||
|
grid_template_columns: RepeatedGridTrack::auto(9), |
||||||
|
overflow: Overflow::scroll_y(), |
||||||
|
..default() |
||||||
|
}, |
||||||
|
BackgroundColor(BLACK.with_alpha(0.4).into()), |
||||||
|
)) |
||||||
|
.observe(|mut event: On<Pointer<Click>>| { |
||||||
|
// Ensure the event doesn't propagate up to the
|
||||||
|
// `Window`, which would grab the mouse cursor.
|
||||||
|
event.propagate(false); |
||||||
|
}) |
||||||
|
.with_children(|builder| { |
||||||
|
for block in blocks.iter() { |
||||||
|
spawn_block_cell(builder, block); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
fn spawn_block_cell(builder: &mut ChildSpawnerCommands, block: &BlockVisuals) { |
||||||
|
let id = block.id().clone(); |
||||||
|
builder |
||||||
|
.spawn(( |
||||||
|
BlockSelectionCell { id }, |
||||||
|
Node { |
||||||
|
width: px(16 * SCALE), |
||||||
|
height: px(16 * SCALE), |
||||||
|
margin: UiRect::all(px(2 * SCALE)), |
||||||
|
padding: UiRect::all(px(2 * SCALE)), |
||||||
|
border: UiRect::all(px(2 * SCALE)), |
||||||
|
..default() |
||||||
|
}, |
||||||
|
BackgroundColor(block.color()), |
||||||
|
BorderColor::all(BLACK), |
||||||
|
)) |
||||||
|
.observe(on_cell_click) |
||||||
|
.observe(on_cell_over) |
||||||
|
.observe(on_cell_out); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Ideally visibility should be toggled when pressing `E`, but this works for now.
|
||||||
|
fn update_selection_visibility( |
||||||
|
cursor: Single<&CursorOptions, Changed<CursorOptions>>, |
||||||
|
crosshair: Single<&mut Visibility, With<BlockSelectionLayout>>, |
||||||
|
) { |
||||||
|
let is_grabbed = cursor.grab_mode != CursorGrabMode::None; |
||||||
|
let mut selection_visibility = crosshair.into_inner(); |
||||||
|
*selection_visibility = is_grabbed.then_some(Visibility::Hidden).unwrap_or_default(); |
||||||
|
} |
||||||
|
|
||||||
|
fn update_selected_cell( |
||||||
|
cells: Query<(&BlockSelectionCell, &mut BorderColor)>, |
||||||
|
selected_block: Res<SelectedBlock>, |
||||||
|
) { |
||||||
|
for (cell, mut color) in cells { |
||||||
|
let is_selected = cell.id == **selected_block; |
||||||
|
*color = if is_selected { WHITE } else { BLACK }.into(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn on_cell_click( |
||||||
|
event: On<Pointer<Click>>, |
||||||
|
mut cells: Query<&BlockSelectionCell>, |
||||||
|
mut commands: Commands, |
||||||
|
) { |
||||||
|
// SAFETY: Entity is known to have this component.
|
||||||
|
let cell = cells.get_mut(event.entity).unwrap(); |
||||||
|
// Resource mightn't've been added yet, so let's just insert / replace it.
|
||||||
|
commands.insert_resource(SelectedBlock(cell.id.clone())); |
||||||
|
} |
||||||
|
|
||||||
|
fn on_cell_over( |
||||||
|
event: On<Pointer<Over>>, |
||||||
|
mut cells: Query<(&BlockSelectionCell, &mut BorderColor)>, |
||||||
|
selected_block: Option<Res<SelectedBlock>>, |
||||||
|
) { |
||||||
|
// SAFETY: Entity is known to have these components.
|
||||||
|
let (cell, mut color) = cells.get_mut(event.entity).unwrap(); |
||||||
|
let is_selected = selected_block.is_some_and(|s| cell.id == **s); |
||||||
|
*color = if is_selected { WHITE } else { GRAY }.into(); |
||||||
|
} |
||||||
|
|
||||||
|
fn on_cell_out( |
||||||
|
event: On<Pointer<Out>>, |
||||||
|
mut cells: Query<(&BlockSelectionCell, &mut BorderColor)>, |
||||||
|
selected_block: Option<Res<SelectedBlock>>, |
||||||
|
) { |
||||||
|
// SAFETY: Entity is known to have these components.
|
||||||
|
let (cell, mut color) = cells.get_mut(event.entity).unwrap(); |
||||||
|
let is_selected = selected_block.is_some_and(|s| cell.id == **s); |
||||||
|
*color = if is_selected { WHITE } else { BLACK }.into(); |
||||||
|
} |
||||||
Loading…
Reference in new issue