free-rusty-maker/src/app/editor/mod.rs

231 lines
6.9 KiB
Rust

use sfml::system::Vector2;
use sfml::window::{Event, Key, mouse::Button as MouseButton};
use sfml::graphics::{Color, RectangleShape, RenderTarget, Transformable, Shape};
use crate::engine::renderer::Renderer;
use crate::engine::map::{CollisionTile, Map};
use crate::engine::texture::SPRITE_SIZE_I32;
use crate::engine::font::Font;
use crate::Result;
/// An action caused by a button.
#[derive(Copy, Clone)]
pub enum Action {
/// Saves the level.
Save,
}
/// A button that can be clicked.
pub struct Button {
/// The label of the button.
pub label: String,
/// The position of the button.
pub position: Vector2<f32>,
/// The size of the button.
pub size: Vector2<f32>,
/// The action of the button.
action: Action,
}
impl Button {
/// Creates a new button.
pub fn new(label: &str, action: Action, x: f32, y: f32, w: f32, h: f32) -> Button {
Button {
action,
label: label.to_owned(),
position: Vector2::new(x, y),
size: Vector2::new(w, h),
}
}
/// Returns the shape of the button.
pub fn shape(&self) -> RectangleShape {
let mut shape = RectangleShape::new();
shape.set_position(self.position + 0.1 * self.size);
shape.set_size(self.size - 0.2 * self.size);
shape.set_fill_color(&Color::rgb(255, 255, 255));
shape
}
/// Draws the button on the renderer.
pub fn render_on(&self, renderer: &mut Renderer) {
renderer.window_mut().draw(&self.shape());
renderer.draw_text(
&self.label,
Font::Sansation,
self.size.y as u32 / 2,
&Color::rgb(0, 0, 0)
);
}
/// Returns true if the coordinates fall inside the shape of the button.
pub fn contains(&self, x: f32, y: f32) -> bool {
let shape = self.shape();
x > shape.position().x && x < shape.position().x + shape.size().x &&
y > shape.position().y && y < shape.position().y + shape.size().y
}
}
/// Represents a level editor.
pub struct Editor {
/// The renderer used by the editor.
renderer: Renderer,
/// The map being currently edited.
map: Map,
/// Indicates whether the editor is running or not.
running: bool,
/// The size of the left panel.
left_panel_size: Vector2<f32>,
/// The size of the top panel.
top_panel_size: Vector2<f32>,
/// The buttons of the menu bar.
menu_bar: Vec<Button>,
}
impl Editor {
/// Creates a new editor.
pub fn new() -> Editor {
let renderer = Renderer::new(800, 600, false);
let menu_bar = vec![
Button::new("Save", Action::Save, 0.0, 0.0, 50.0, 50.0),
];
Editor {
map: Map::new(50, 50),
running: true,
top_panel_size: Vector2::new(renderer.window().size().x as f32, 50.0),
left_panel_size: Vector2::new(100.0, renderer.window().size().y as f32 - 50.0),
renderer,
menu_bar,
}
}
/// Runs the main loop of the editor.
pub fn run(&mut self) {
while self.running {
while let Some(event) = self.renderer.poll_event() {
self.manage_event(event);
}
self.render();
}
}
/// Manages the event occuring on the display.
pub fn manage_event(&mut self, event: Event) {
match event {
// Quit the game if window is closed
Event::Closed => self.running = false,
// Quit the game if escape is pressed
Event::KeyPressed {
code: Key::Escape, ..
} => self.running = false,
// A click on the screen
Event::MouseButtonPressed {
button: MouseButton::Left, x, y,
} => {
if x as f32 >= self.left_panel_size.x && y as f32 >= self.top_panel_size.y {
let x = ((x - self.left_panel_size.x as i32) / SPRITE_SIZE_I32) as usize;
let y = ((y - self.top_panel_size.y as i32) / SPRITE_SIZE_I32) as usize;
if let Some(tile) = self.map.collision_tile(y, x) {
if tile.is_empty() {
self.map.set_tile(y, x, CollisionTile::full());
} else {
self.map.set_tile(y, x, CollisionTile::empty());
}
}
}
let mut actions = vec![];
for button in &self.menu_bar {
if button.contains(x as f32, y as f32) {
actions.push(button.action);
}
}
for action in actions {
if let Err(e) = self.apply_action(action) {
eprintln!("warning: {}", e);
}
}
},
_ => (),
}
}
/// Performs the rendering on the screen.
pub fn render(&mut self) {
self.renderer.clear();
// Show the top panel
let mut top_panel = RectangleShape::with_size(self.top_panel_size);
top_panel.set_fill_color(&Color::rgb(255, 0, 0));
self.renderer.window_mut().draw(&top_panel);
for button in &self.menu_bar {
button.render_on(&mut self.renderer);
}
// Show the left panel
let mut left_panel = RectangleShape::with_size(self.left_panel_size);
left_panel.set_position((0.0, self.top_panel_size.y));
left_panel.set_fill_color(&Color::rgb(0, 255, 0));
self.renderer.window_mut().draw(&left_panel);
// Show the map
for i in 0 .. self.map.rows() {
for j in 0 .. self.map.cols() {
let tile = self.map.at(i, j);
if tile.graphic.is_visible() {
self.renderer.translate_and_draw(
&tile,
(self.left_panel_size.x, self.top_panel_size.y)
);
}
}
}
// Draw the border of the map
let mut border = RectangleShape::new();
border.set_size(((
self.map.cols() * SPRITE_SIZE_I32 as usize) as f32,
(self.map.rows() * SPRITE_SIZE_I32 as usize) as f32
));
border.set_position((self.left_panel_size.x, self.top_panel_size.y));
border.set_fill_color(&Color::rgba(0, 0, 0, 0));
border.set_outline_color(&Color::rgb(0, 0, 0));
border.set_outline_thickness(2.0);
self.renderer.window_mut().draw(&border);
// Display and manage the frame rate
self.renderer.display();
}
/// Saves the level.
pub fn export(&self) -> Result<()> {
self.map.save("level.lvl")
}
/// Applies the corresponding action.
pub fn apply_action(&mut self, action: Action) -> Result<()> {
match action {
Action::Save => self.export()
}
}
}