354 lines
11 KiB
Rust
354 lines
11 KiB
Rust
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use downcast::Downcast;
|
|
use imgui::{Condition, MenuItem, TabItem, TabItemFlags, Window};
|
|
|
|
use crate::editor::{CurrentTool, EditorInstance};
|
|
use crate::framework::context::Context;
|
|
use crate::framework::error::GameResult;
|
|
use crate::framework::keyboard;
|
|
use crate::framework::keyboard::ScanCode;
|
|
use crate::framework::ui::Components;
|
|
use crate::game::shared_game_state::SharedGameState;
|
|
use crate::game::stage::Stage;
|
|
use crate::graphics::font::Font;
|
|
use crate::scene::Scene;
|
|
use crate::scene::game_scene::GameScene;
|
|
use crate::scene::title_scene::TitleScene;
|
|
|
|
struct ErrorList {
|
|
errors: Vec<String>,
|
|
}
|
|
|
|
impl ErrorList {
|
|
fn new() -> ErrorList {
|
|
Self { errors: Vec::new() }
|
|
}
|
|
|
|
fn try_or_push_error(&mut self, func: impl FnOnce() -> GameResult<()>) {
|
|
if let Err(err) = func() {
|
|
self.errors.push(err.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn catch(error_list: Rc<RefCell<ErrorList>>, func: impl FnOnce() -> GameResult<()>) {
|
|
error_list.borrow_mut().try_or_push_error(func);
|
|
}
|
|
|
|
pub struct EditorScene {
|
|
stage_list: StageListWindow,
|
|
error_list: Rc<RefCell<ErrorList>>,
|
|
instances: Vec<EditorInstance>,
|
|
subscene: Option<Box<GameScene>>,
|
|
current_tool: CurrentTool,
|
|
selected_instance: usize,
|
|
switch_tab: bool,
|
|
}
|
|
|
|
impl EditorScene {
|
|
pub fn new() -> Self {
|
|
EditorScene {
|
|
stage_list: StageListWindow::new(),
|
|
error_list: Rc::new(RefCell::new(ErrorList::new())),
|
|
instances: Vec::new(),
|
|
subscene: None,
|
|
current_tool: CurrentTool::Move,
|
|
selected_instance: 0,
|
|
switch_tab: false,
|
|
}
|
|
}
|
|
|
|
fn exit_editor(&mut self, state: &mut SharedGameState) {
|
|
state.next_scene = Some(Box::new(TitleScene::new()));
|
|
}
|
|
|
|
fn open_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context, stage_id: usize) {
|
|
catch(self.error_list.clone(), || {
|
|
for (idx, instance) in self.instances.iter().enumerate() {
|
|
if instance.stage_id == stage_id {
|
|
self.selected_instance = idx;
|
|
self.switch_tab = true;
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
if let Some(stage) = state.stages.get(stage_id) {
|
|
let stage = Stage::load(&state.constants.base_paths, stage, ctx)?;
|
|
|
|
let new_instance = EditorInstance::new(stage_id, stage);
|
|
self.instances.push(new_instance);
|
|
self.selected_instance = self.instances.len() - 1;
|
|
self.switch_tab = true;
|
|
}
|
|
|
|
Ok(())
|
|
});
|
|
}
|
|
|
|
fn test_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
|
|
catch(self.error_list.clone(), || {
|
|
if let Some(instance) = self.instances.get(self.selected_instance) {
|
|
state.reset();
|
|
state.textscript_vm.start_script(94);
|
|
let mut game_scene = GameScene::from_stage(state, ctx, instance.stage.clone(), instance.stage_id)?;
|
|
game_scene.init(state, ctx)?;
|
|
game_scene.player1.cond.set_alive(true);
|
|
game_scene.player1.x = instance.frame.x + (state.canvas_size.0 * 256.0) as i32;
|
|
game_scene.player1.y = instance.frame.y + (state.canvas_size.1 * 256.0) as i32;
|
|
state.control_flags.set_control_enabled(true);
|
|
state.control_flags.set_tick_world(true);
|
|
state.textscript_vm.suspend = false;
|
|
self.subscene = Some(Box::new(game_scene));
|
|
}
|
|
|
|
Ok(())
|
|
});
|
|
}
|
|
|
|
fn perform_actions(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
|
|
let actions = std::mem::take(&mut self.stage_list.actions);
|
|
for action in actions.iter() {
|
|
match action {
|
|
StageListAction::OpenStage(idx) => self.open_stage(state, ctx, *idx),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
trait ExtraWidgetsExt {
|
|
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool;
|
|
}
|
|
|
|
impl ExtraWidgetsExt for imgui::Ui<'_> {
|
|
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool {
|
|
if active {
|
|
let color = self.style_color(imgui::StyleColor::ButtonActive);
|
|
let _token1 = self.push_style_color(imgui::StyleColor::Button, color);
|
|
let _token2 = self.push_style_color(imgui::StyleColor::ButtonHovered, color);
|
|
let ret = self.button(label);
|
|
ret
|
|
} else {
|
|
self.button(label)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Scene for EditorScene {
|
|
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
let subscene_ref = &mut self.subscene;
|
|
if subscene_ref.is_some() {
|
|
subscene_ref.as_mut().unwrap().tick(state, ctx)?;
|
|
|
|
if keyboard::is_key_pressed(ctx, ScanCode::Escape) {
|
|
*subscene_ref = None;
|
|
}
|
|
|
|
// hijack scene switches
|
|
let next_scene = std::mem::take(&mut state.next_scene);
|
|
if let Some(next_scene) = next_scene {
|
|
*subscene_ref = if let Ok(game_scene) = next_scene.downcast() {
|
|
let mut game_scene: Box<GameScene> = game_scene;
|
|
game_scene.init(state, ctx)?;
|
|
Some(game_scene)
|
|
} else {
|
|
None
|
|
};
|
|
}
|
|
|
|
if subscene_ref.is_none() {
|
|
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
|
|
}
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn draw_tick(&mut self, state: &mut SharedGameState) -> GameResult {
|
|
if let Some(scene) = &mut self.subscene {
|
|
scene.draw_tick(state)?;
|
|
return Ok(());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
if let Some(scene) = &self.subscene {
|
|
scene.draw(state, ctx)?;
|
|
|
|
state.font.builder().position(4.0, 4.0).draw(
|
|
"Press [ESC] to return.",
|
|
ctx,
|
|
&state.constants,
|
|
&mut state.texture_set,
|
|
)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(instance) = self.instances.get(self.selected_instance) {
|
|
instance.draw(state, ctx, self.current_tool)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn imgui_draw(
|
|
&mut self,
|
|
_game_ui: &mut Components,
|
|
state: &mut SharedGameState,
|
|
ctx: &mut Context,
|
|
ui: &mut imgui::Ui,
|
|
) -> GameResult {
|
|
self.perform_actions(state, ctx);
|
|
|
|
if let Some(_) = self.subscene {
|
|
return Ok(());
|
|
}
|
|
|
|
let mut menu_bar_size = (0.0, 0.0);
|
|
if let Some(menu_bar) = ui.begin_main_menu_bar() {
|
|
let [menu_bar_w, menu_bar_h] = ui.window_size();
|
|
menu_bar_size = (menu_bar_w, menu_bar_h);
|
|
|
|
if let Some(menu) = ui.begin_menu("File") {
|
|
if MenuItem::new("Open stage").shortcut("Ctrl+O").build(ui) {
|
|
self.stage_list.show();
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
if MenuItem::new("Exit editor").build(ui) {
|
|
self.exit_editor(state);
|
|
}
|
|
|
|
menu.end();
|
|
}
|
|
menu_bar.end();
|
|
}
|
|
|
|
Window::new("Toolbar")
|
|
.title_bar(false)
|
|
.resizable(false)
|
|
.position([0.0, menu_bar_size.1], Condition::Always)
|
|
.size([menu_bar_size.0, 0.0], Condition::Always)
|
|
.build(ui, || {
|
|
if ui.tool_button("Move", self.current_tool == CurrentTool::Move) {
|
|
self.current_tool = CurrentTool::Move;
|
|
}
|
|
ui.same_line();
|
|
if ui.tool_button("Brush", self.current_tool == CurrentTool::Brush) {
|
|
self.current_tool = CurrentTool::Brush;
|
|
}
|
|
ui.same_line();
|
|
if ui.tool_button("Fill", self.current_tool == CurrentTool::Fill) {
|
|
self.current_tool = CurrentTool::Fill;
|
|
}
|
|
ui.same_line();
|
|
if ui.tool_button("Rectangle", self.current_tool == CurrentTool::Rectangle) {
|
|
self.current_tool = CurrentTool::Rectangle;
|
|
}
|
|
|
|
ui.same_line();
|
|
ui.text("|");
|
|
|
|
ui.same_line();
|
|
if ui.button("Test Stage") {
|
|
self.test_stage(state, ctx);
|
|
}
|
|
|
|
if let Some(tab) = ui.tab_bar("Stages") {
|
|
for (idx, inst) in self.instances.iter().enumerate() {
|
|
let mut flags = TabItemFlags::NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON;
|
|
if self.switch_tab && self.selected_instance == idx {
|
|
self.switch_tab = false;
|
|
flags |= TabItemFlags::SET_SELECTED;
|
|
}
|
|
|
|
if let Some(item) = TabItem::new(&inst.stage.data.name).flags(flags).begin(ui) {
|
|
if !self.switch_tab {
|
|
self.selected_instance = idx;
|
|
}
|
|
item.end();
|
|
}
|
|
}
|
|
|
|
tab.end();
|
|
}
|
|
});
|
|
|
|
self.stage_list.action(state, ctx, ui);
|
|
|
|
if let Some(instance) = self.instances.get_mut(self.selected_instance) {
|
|
instance.process(state, ctx, ui, self.current_tool);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct StageListWindow {
|
|
visible: bool,
|
|
selected_stage: i32,
|
|
actions: Vec<StageListAction>,
|
|
}
|
|
|
|
enum StageListAction {
|
|
OpenStage(usize),
|
|
}
|
|
|
|
impl StageListWindow {
|
|
fn new() -> Self {
|
|
StageListWindow { visible: false, selected_stage: 0, actions: Vec::new() }
|
|
}
|
|
|
|
fn show(&mut self) {
|
|
self.visible = true;
|
|
}
|
|
|
|
fn action(&mut self, state: &mut SharedGameState, _ctx: &mut Context, ui: &mut imgui::Ui) {
|
|
if !self.visible {
|
|
return;
|
|
}
|
|
|
|
Window::new("Stage list")
|
|
.resizable(false)
|
|
.collapsible(false)
|
|
.position_pivot([0.5, 0.5])
|
|
.size([300.0, 352.0], Condition::FirstUseEver)
|
|
.build(ui, || {
|
|
let mut stages = Vec::with_capacity(state.stages.len());
|
|
for stage in state.stages.iter() {
|
|
stages.push(stage.name.as_str());
|
|
}
|
|
|
|
ui.push_item_width(-1.0);
|
|
ui.list_box("", &mut self.selected_stage, &stages, 14);
|
|
|
|
ui.disabled(self.selected_stage < 0, || {
|
|
if ui.button("Open") {
|
|
self.actions.push(StageListAction::OpenStage(self.selected_stage as usize));
|
|
}
|
|
|
|
ui.same_line();
|
|
if ui.button("Edit table entry") {}
|
|
});
|
|
|
|
ui.same_line();
|
|
if ui.button("Cancel") {
|
|
self.visible = false;
|
|
}
|
|
});
|
|
}
|
|
}
|