2022-01-06 01:11:17 +00:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::ops::Deref;
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
use imgui::{Image, MouseButton, Window, WindowFlags};
|
|
|
|
|
|
|
|
use crate::common::{Color, Rect};
|
|
|
|
use crate::components::background::Background;
|
|
|
|
use crate::components::tilemap::{TileLayer, Tilemap};
|
|
|
|
use crate::frame::Frame;
|
|
|
|
use crate::stage::{Stage, StageTexturePaths};
|
|
|
|
use crate::{graphics, Context, GameResult, SharedGameState, I_MAG};
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
|
|
pub enum CurrentTool {
|
|
|
|
Move,
|
|
|
|
Brush,
|
|
|
|
Fill,
|
|
|
|
Rectangle,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct EditorInstance {
|
|
|
|
pub stage: Stage,
|
|
|
|
pub stage_id: usize,
|
|
|
|
pub frame: Frame,
|
|
|
|
pub background: Background,
|
|
|
|
pub stage_textures: Rc<RefCell<StageTexturePaths>>,
|
|
|
|
pub tilemap: Tilemap,
|
|
|
|
pub zoom: f32,
|
|
|
|
pub current_tile: u8,
|
|
|
|
pub mouse_pos: (f32, f32),
|
|
|
|
pub want_capture_mouse: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EditorInstance {
|
|
|
|
pub fn new(stage_id: usize, stage: Stage) -> EditorInstance {
|
|
|
|
let stage_textures = {
|
|
|
|
let mut textures = StageTexturePaths::new();
|
|
|
|
textures.update(&stage);
|
|
|
|
Rc::new(RefCell::new(textures))
|
|
|
|
};
|
|
|
|
let mut frame = Frame::new();
|
|
|
|
frame.x = -16 * 0x200;
|
|
|
|
frame.y = -48 * 0x200;
|
|
|
|
|
|
|
|
EditorInstance {
|
|
|
|
stage,
|
|
|
|
stage_id,
|
|
|
|
frame,
|
|
|
|
background: Background::new(),
|
|
|
|
stage_textures,
|
|
|
|
tilemap: Tilemap::new(),
|
|
|
|
zoom: 2.0,
|
|
|
|
current_tile: 0,
|
|
|
|
mouse_pos: (0.0, 0.0),
|
|
|
|
want_capture_mouse: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui, tool: CurrentTool) {
|
|
|
|
self.frame.prev_x = self.frame.x;
|
|
|
|
self.frame.prev_y = self.frame.y;
|
|
|
|
self.mouse_pos = (ui.io().mouse_pos[0], ui.io().mouse_pos[1]);
|
|
|
|
self.want_capture_mouse = ui.io().want_capture_mouse;
|
|
|
|
|
|
|
|
let mut drag = false;
|
|
|
|
|
|
|
|
match tool {
|
|
|
|
CurrentTool::Move => {
|
|
|
|
if ui.io().want_capture_mouse {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
drag |= ui.is_mouse_down(MouseButton::Left) || ui.is_mouse_down(MouseButton::Right);
|
|
|
|
}
|
|
|
|
CurrentTool::Brush => {
|
|
|
|
self.palette_window(state, ctx, ui);
|
|
|
|
|
|
|
|
if ui.io().want_capture_mouse {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
drag |= ui.is_mouse_down(MouseButton::Right);
|
|
|
|
|
|
|
|
if !drag && ui.is_mouse_down(MouseButton::Left) {
|
|
|
|
let tile_size = self.stage.map.tile_size.as_int();
|
|
|
|
let halft = tile_size / 2;
|
|
|
|
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
|
|
|
|
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
|
|
|
|
let tile_x = stage_mouse_x / tile_size;
|
|
|
|
let tile_y = stage_mouse_y / tile_size;
|
|
|
|
|
|
|
|
if tile_x >= 0
|
|
|
|
&& tile_y >= 0
|
|
|
|
&& tile_x < self.stage.map.width as i32
|
|
|
|
&& tile_y < self.stage.map.height as i32
|
|
|
|
{
|
|
|
|
self.stage.change_tile(tile_x as usize, tile_y as usize, self.current_tile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CurrentTool::Fill => {
|
|
|
|
self.palette_window(state, ctx, ui);
|
|
|
|
drag |= ui.is_mouse_down(MouseButton::Right);
|
|
|
|
}
|
|
|
|
CurrentTool::Rectangle => {
|
|
|
|
self.palette_window(state, ctx, ui);
|
|
|
|
drag |= ui.is_mouse_down(MouseButton::Right);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if drag {
|
|
|
|
self.frame.x -= (512.0 * ui.io().mouse_delta[0] as f32 / self.zoom) as i32;
|
|
|
|
self.frame.y -= (512.0 * ui.io().mouse_delta[1] as f32 / self.zoom) as i32;
|
|
|
|
self.frame.prev_x = self.frame.x;
|
|
|
|
self.frame.prev_y = self.frame.y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tile_cursor(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
|
|
if self.want_capture_mouse {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let tile_size = self.stage.map.tile_size.as_int();
|
|
|
|
let halft = tile_size / 2;
|
|
|
|
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
|
|
|
|
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
|
|
|
|
let tile_x = stage_mouse_x / tile_size;
|
|
|
|
let tile_y = stage_mouse_y / tile_size;
|
|
|
|
let frame_x = self.frame.x as f32 / 512.0;
|
|
|
|
let frame_y = self.frame.y as f32 / 512.0;
|
|
|
|
|
|
|
|
if tile_x < 0 || tile_y < 0 || tile_x >= self.stage.map.width as i32 || tile_y >= self.stage.map.height as i32 {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let name = &self.stage_textures.deref().borrow().tileset_fg;
|
|
|
|
|
|
|
|
if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, name) {
|
|
|
|
let tile_size16 = tile_size as u16;
|
|
|
|
let rect = Rect::new_size(
|
|
|
|
(self.current_tile as u16 % 16) * tile_size16,
|
|
|
|
(self.current_tile as u16 / 16) * tile_size16,
|
|
|
|
tile_size16,
|
|
|
|
tile_size16,
|
|
|
|
);
|
|
|
|
|
|
|
|
batch.add_rect_tinted(
|
|
|
|
(tile_x * tile_size - halft) as f32 - frame_x,
|
|
|
|
(tile_y * tile_size - halft) as f32 - frame_y,
|
|
|
|
(255, 255, 255, 192),
|
|
|
|
&rect,
|
|
|
|
);
|
|
|
|
|
|
|
|
batch.draw(ctx)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn palette_window(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &imgui::Ui) {
|
|
|
|
Window::new("Palette")
|
|
|
|
.size([260.0, 260.0], imgui::Condition::Always)
|
|
|
|
.position(ui.io().display_size, imgui::Condition::FirstUseEver)
|
|
|
|
.position_pivot([1.0, 1.0])
|
|
|
|
.resizable(false)
|
|
|
|
.build(ui, || {
|
|
|
|
let name = &self.stage_textures.deref().borrow().tileset_fg;
|
|
|
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, name);
|
|
|
|
|
|
|
|
let pos = ui.cursor_screen_pos();
|
|
|
|
let tile_size = self.stage.map.tile_size.as_float();
|
|
|
|
|
|
|
|
if let Ok(batch) = batch {
|
|
|
|
let (scale_x, scale_y) = batch.scale();
|
|
|
|
if let Some(tex) = batch.get_texture() {
|
|
|
|
let (width, height) = tex.dimensions();
|
|
|
|
let (width, height) = (width as f32 / scale_x, height as f32 / scale_y);
|
|
|
|
|
|
|
|
if let Ok(tex_id) = graphics::imgui_texture_id(ctx, tex) {
|
|
|
|
Image::new(tex_id, [width, height]).build(ui);
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.set_cursor_screen_pos(pos);
|
|
|
|
ui.invisible_button("##tiles", [width, height]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let draw_list = ui.get_window_draw_list();
|
|
|
|
let cur_pos1 = [
|
|
|
|
pos[0].floor() + tile_size * (self.current_tile % 16) as f32,
|
|
|
|
pos[1].floor() + tile_size * (self.current_tile / 16) as f32,
|
|
|
|
];
|
|
|
|
let cur_pos2 = [cur_pos1[0] + tile_size, cur_pos1[1] + tile_size];
|
|
|
|
draw_list.add_rect(cur_pos1, cur_pos2, [1.0, 0.0, 0.0, 1.0]).thickness(2.0).build();
|
|
|
|
|
|
|
|
if ui.is_mouse_down(MouseButton::Left) {
|
|
|
|
let mouse_pos = ui.io().mouse_pos;
|
|
|
|
let x = (mouse_pos[0] - pos[0]) / tile_size;
|
|
|
|
let y = (mouse_pos[1] - pos[1]) / tile_size;
|
|
|
|
|
|
|
|
if x >= 0.0 && x < 16.0 && y >= 0.0 && y < 16.0 {
|
|
|
|
self.current_tile = (y as u8 * 16 + x as u8) as u8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, tool: CurrentTool) -> GameResult {
|
|
|
|
let old_scale = state.scale;
|
|
|
|
set_scale(state, self.zoom);
|
|
|
|
|
|
|
|
let paths = self.stage_textures.deref().borrow();
|
|
|
|
self.background.draw(state, ctx, &self.frame, &*paths, &self.stage)?;
|
|
|
|
|
2022-01-25 05:16:07 +00:00
|
|
|
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Background, &*paths, &self.stage)?;
|
|
|
|
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Middleground, &*paths, &self.stage)?;
|
|
|
|
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Foreground, &*paths, &self.stage)?;
|
|
|
|
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Snack, &*paths, &self.stage)?;
|
2022-01-06 01:11:17 +00:00
|
|
|
|
|
|
|
self.draw_black_bars(state, ctx)?;
|
|
|
|
|
|
|
|
match tool {
|
|
|
|
CurrentTool::Move => (),
|
|
|
|
CurrentTool::Brush | CurrentTool::Fill | CurrentTool::Rectangle => {
|
|
|
|
self.tile_cursor(state, ctx)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set_scale(state, old_scale);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_black_bars(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
|
|
let color = Color::from_rgba(0, 0, 0, 128);
|
|
|
|
let (x, y) = self.frame.xy_interpolated(state.frame_time);
|
|
|
|
let (x, y) = (x * state.scale, y * state.scale);
|
|
|
|
let canvas_w_scaled = state.canvas_size.0 as f32 * state.scale;
|
|
|
|
let canvas_h_scaled = state.canvas_size.1 as f32 * state.scale;
|
|
|
|
let level_width = (self.stage.map.width as f32 - 1.0) * self.stage.map.tile_size.as_float();
|
|
|
|
let level_height = (self.stage.map.height as f32 - 1.0) * self.stage.map.tile_size.as_float();
|
|
|
|
let left_side = -x;
|
|
|
|
let right_side = -x + level_width * state.scale;
|
|
|
|
let upper_side = -y;
|
|
|
|
let lower_side = -y + level_height * state.scale;
|
|
|
|
|
|
|
|
if left_side > 0.0 {
|
|
|
|
let rect = Rect::new(0, upper_side as isize, left_side as isize, lower_side as isize);
|
|
|
|
graphics::draw_rect(ctx, rect, color)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if right_side < canvas_w_scaled {
|
|
|
|
let rect = Rect::new(
|
|
|
|
right_side as isize,
|
|
|
|
upper_side as isize,
|
|
|
|
(state.canvas_size.0 * state.scale) as isize,
|
|
|
|
lower_side as isize,
|
|
|
|
);
|
|
|
|
graphics::draw_rect(ctx, rect, color)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if upper_side > 0.0 {
|
|
|
|
let rect = Rect::new(0, 0, canvas_w_scaled as isize, upper_side as isize);
|
|
|
|
graphics::draw_rect(ctx, rect, color)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if lower_side < canvas_h_scaled {
|
|
|
|
let rect = Rect::new(0, lower_side as isize, canvas_w_scaled as isize, canvas_h_scaled as isize);
|
|
|
|
graphics::draw_rect(ctx, rect, color)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_scale(state: &mut SharedGameState, scale: f32) {
|
|
|
|
state.scale = scale;
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
I_MAG = state.scale;
|
|
|
|
state.canvas_size = (state.screen_size.0 / state.scale, state.screen_size.1 / state.scale);
|
|
|
|
}
|
|
|
|
}
|