doukutsu-rs/src/editor/mod.rs

286 lines
10 KiB
Rust

use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use imgui::{Image, MouseButton, Window, WindowFlags};
use crate::{Context, GameResult, graphics, I_MAG, SharedGameState};
use crate::common::{Color, Rect};
use crate::components::background::Background;
use crate::components::tilemap::{TileLayer, Tilemap};
use crate::game::frame::Frame;
use crate::game::stage::{Stage, StageTexturePaths};
#[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)?;
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)?;
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);
}
}