/* Moonfall Copyright (C) 2008 Alex Yatskov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Pch.h" #include "FrameSpriteEditor.h" BEGIN_EVENT_TABLE(FrameSpriteEditor, wxFrame) EVT_CLOSE( FrameSpriteEditor::OnClose) EVT_MENU(ID_MENU_FILE_NEW, FrameSpriteEditor::OnMenuSelectedFileNew) EVT_MENU(ID_MENU_FILE_OPEN, FrameSpriteEditor::OnMenuSelectedFileOpen) EVT_MENU(ID_MENU_FILE_SAVE, FrameSpriteEditor::OnMenuSelectedFileSave) EVT_MENU(ID_MENU_FILE_SAVE_AS, FrameSpriteEditor::OnMenuSelectedFileSaveAs) EVT_MENU(ID_MENU_FILE_EXIT, FrameSpriteEditor::OnMenuSelectedFileExit) EVT_MENU(ID_MENU_IMAGE_INSERT, FrameSpriteEditor::OnMenuSelectedImageInsert) EVT_MENU(ID_MENU_IMAGE_DELETE, FrameSpriteEditor::OnMenuSelectedImageDelete) EVT_MENU(ID_MENU_SPRITE_RENAME, FrameSpriteEditor::OnMenuSelectedSpriteRename) EVT_MENU(ID_MENU_SPRITE_DELETE, FrameSpriteEditor::OnMenuSelectedSpriteDelete) EVT_MENU(ID_MENU_SHAPE_RENAME, FrameSpriteEditor::OnMenuSelectedShapeRename) EVT_MENU(ID_MENU_SHAPE_DELETE, FrameSpriteEditor::OnMenuSelectedShapeDelete) END_EVENT_TABLE() FrameSpriteEditor::FrameSpriteEditor(wxWindow* parent, const wxString& manifestFilename) : wxFrame(parent, wxID_ANY, wxT("Sprite Editor"), wxDefaultPosition, wxSize(800, 600)), m_listImages(NULL), m_listSprites(NULL), m_listShapes(NULL), m_scrolledWindow(NULL), m_panelCanvas(NULL), m_textGrid(NULL), m_draggingSprite(false), m_draggingShape(false), m_gridSize(16), m_imageIndex(wxNOT_FOUND), m_spriteIndex(wxNOT_FOUND), m_shapeIndex(wxNOT_FOUND) { InitializeUi(); InitializeMenuBar(); m_currentFilename = manifestFilename; if (!manifestFilename.IsEmpty() && LoadManifest(manifestFilename)) { UpdateImageList(); UpdateSpriteList(); UpdateShapeList(); UpdateCanvas(); } } bool FrameSpriteEditor::LoadManifest(const wxString& filename) { ClearManifest(); if (!m_spriteManager.LoadManifest(filename.ToAscii().data())) { return false; } std::vector summary; m_spriteManager.SummarizeSprites(&summary); for (std::vector::iterator summaryIter = summary.begin(); summaryIter != summary.end(); ++summaryIter) { std::vector::iterator imageIter; for (imageIter = m_images.begin(); imageIter != m_images.end(); ++imageIter) { if (imageIter->filename == summaryIter->asset) { break; } } if (imageIter == m_images.end()) { m_images.push_back(Image(summaryIter->asset)); imageIter = m_images.begin() + m_images.size() - 1; } imageIter->sprites.push_back(Sprite(summaryIter->alias, summaryIter->position, summaryIter->size, summaryIter->shapes)); } return true; } bool FrameSpriteEditor::SaveManifest(const wxString& filename) { m_spriteManager.ClearManifest(); for (std::vector::iterator imageIter = m_images.begin(); imageIter != m_images.end(); ++imageIter) { for (std::vector::iterator spriteIter = imageIter->sprites.begin(); spriteIter != imageIter->sprites.end(); ++spriteIter) { m_spriteManager.AppendManifest( spriteIter->alias.c_str(), imageIter->filename.c_str(), spriteIter->position, spriteIter->size, spriteIter->shapes ); } } return m_spriteManager.SaveManifest(filename.ToAscii().data()); } void FrameSpriteEditor::ClearManifest() { m_spriteManager.ClearManifest(); m_images.clear(); } void FrameSpriteEditor::OnMenuSelectedFileNew(wxCommandEvent& event) { m_currentFilename.Empty(); ClearManifest(); UpdateImageList(); UpdateSpriteList(); UpdateShapeList(); UpdateCanvas(); } void FrameSpriteEditor::OnMenuSelectedFileOpen(wxCommandEvent& event) { const wxString filename = wxFileSelector( wxT("Choose a sprite manifest file"), wxEmptyString, wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"), wxFD_OPEN, this ); if (filename.IsEmpty()) { return; } m_currentFilename = filename; LoadManifest(filename); UpdateImageList(); UpdateSpriteList(); UpdateShapeList(); UpdateCanvas(); } void FrameSpriteEditor::OnMenuSelectedFileSave(wxCommandEvent& event) { if (m_currentFilename.IsEmpty()) { OnMenuSelectedFileSaveAs(event); } else { SaveManifest(m_currentFilename); } } void FrameSpriteEditor::OnMenuSelectedFileSaveAs(wxCommandEvent& event) { const wxString filename = wxFileSelector( wxT("Choose a sprite manifest file"), wxEmptyString, wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"), wxFD_SAVE, this ); if (filename.IsEmpty()) { return; } m_currentFilename = filename; SaveManifest(filename); } void FrameSpriteEditor::OnMenuSelectedFileExit(wxCommandEvent& event) { Hide(); } void FrameSpriteEditor::OnCanvasLeftDown(wxMouseEvent& event) { if (m_imageIndex != wxNOT_FOUND) { if (!event.ControlDown()) { m_draggingSprite = true; } else if (m_spriteIndex != wxNOT_FOUND) { m_draggingShape = true; } } m_marquee = Marquee(AdjustPosition(Vector2i(event.GetX(), event.GetY())), Vector2i(0, 0)); UpdateCanvas(); } void FrameSpriteEditor::OnCanvasLeftUp(wxMouseEvent& event) { do { if (m_imageIndex == wxNOT_FOUND || m_marquee.size.x == 0 || m_marquee.size.y == 0) { break; } Image& image = m_images[m_imageIndex]; if (m_draggingSprite) { wxTextEntryDialog dialog(this, wxT("Sprite alias"), wxT("Input a sprite alias")); if (dialog.ShowModal() != wxID_OK || dialog.GetValue().IsEmpty()) { break; } image.sprites.push_back(Sprite(dialog.GetValue().ToAscii().data(), m_marquee.position, m_marquee.size)); UpdateSpriteList(); } else if (m_draggingShape && m_spriteIndex != wxNOT_FOUND) { Sprite& sprite = image.sprites[m_spriteIndex]; long shapeType = 0; wxTextEntryDialog dialogId(this, wxT("Shape type"), wxT("Input shape type")); if (dialogId.ShowModal() != wxID_OK || !dialogId.GetValue().ToLong(&shapeType) || shapeType >= ACTOR_SHAPE_TYPE_COUNT) { break; } long shapeData = 0; wxTextEntryDialog dialogData(this, wxT("Shape data"), wxT("Input shape data")); if (dialogData.ShowModal() != wxID_OK || !dialogData.GetValue().ToLong(&shapeData)) { break; } ActorShape& shape = sprite.shapes[shapeType]; shape.data = shapeData; shape.position = m_marquee.position - sprite.position; shape.size = m_marquee.size; shape.enabled = true;; UpdateShapeList(); } } while (false); m_draggingSprite = false; m_draggingShape = false; UpdateCanvas(); } void FrameSpriteEditor::OnCanvasRightDown(wxMouseEvent& event) { if (m_draggingSprite || m_draggingShape || m_imageIndex == wxNOT_FOUND) { return; } const Image& image = m_images[m_imageIndex]; for (size_t i = 0; i < image.sprites.size(); ++i) { const Sprite& sprite = image.sprites[i]; const Rect4i spriteRect(sprite.position.x, sprite.position.y, sprite.position.x + sprite.size.x, sprite.position.y + sprite.size.y); if (spriteRect.Contains(event.GetX(), event.GetY())) { m_spriteIndex = static_cast(i); m_listSprites->SetSelection(m_spriteIndex); UpdateShapeList(); UpdateCanvas(); break; } } } void FrameSpriteEditor::OnCanvasMotion(wxMouseEvent& event) { if (!m_draggingSprite && !m_draggingShape) { return; } Vector2i position = AdjustPosition(Vector2i(event.GetX(), event.GetY())); position.x = MAX(position.x, m_marquee.position.x); position.y = MAX(position.y, m_marquee.position.y); m_marquee.size = position - m_marquee.position; UpdateCanvas(); } void FrameSpriteEditor::OnCanvasPaint(wxPaintEvent& event) { wxBufferedPaintDC dc(m_panelCanvas); dc.SetBackground(wxBrush(wxColor(0x00, 0x00, 0x00), wxSOLID)); dc.Clear(); if (m_currentBitmap.IsOk()) { dc.SetBrush(wxBrush(wxColor(0x00, 0x00, 0xff), wxSOLID)); dc.SetPen(wxPen(wxColor(0x00, 0x00, 0x00), 1, wxTRANSPARENT)); dc.DrawRectangle(BITMAP_OFFSET, BITMAP_OFFSET, m_currentBitmap.GetWidth(), m_currentBitmap.GetHeight()); dc.DrawBitmap(m_currentBitmap, BITMAP_OFFSET, BITMAP_OFFSET, true); } dc.SetBrush(wxBrush(wxColor(0x00, 0x00, 0x00), wxTRANSPARENT)); if (m_draggingSprite) { dc.SetPen(wxPen(wxColor(0x00, 0xff, 0x00), 2, wxSOLID)); dc.DrawRectangle(m_marquee.position.x + BITMAP_OFFSET, m_marquee.position.y + BITMAP_OFFSET, m_marquee.size.x, m_marquee.size.y); } else if (m_draggingShape) { dc.SetPen(wxPen(wxColor(0xff, 0xff, 0x00), 2, wxSOLID)); dc.DrawRectangle(m_marquee.position.x + BITMAP_OFFSET, m_marquee.position.y + BITMAP_OFFSET, m_marquee.size.x, m_marquee.size.y); } else if (m_spriteIndex != wxNOT_FOUND && m_imageIndex != wxNOT_FOUND) { const Sprite& sprite = m_images[m_imageIndex].sprites[m_spriteIndex]; dc.SetPen(wxPen(wxColor(0x00, 0xff, 0x00), 2, wxSHORT_DASH)); dc.DrawRectangle(sprite.position.x + BITMAP_OFFSET, sprite.position.y + BITMAP_OFFSET, sprite.size.x, sprite.size.y); if (m_shapeIndex != wxNOT_FOUND) { const ActorShape& shape = sprite.shapes[m_shapeIndex]; if (shape.enabled) { dc.SetPen(wxPen(wxColor(0xff, 0xff, 0x00), 2, wxSHORT_DASH)); dc.DrawRectangle(shape.position.x + sprite.position.x + BITMAP_OFFSET, shape.position.y + sprite.position.y + BITMAP_OFFSET, shape.size.x, shape.size.y); } } } } void FrameSpriteEditor::OnMenuSelectedImageInsert(wxCommandEvent& event) { wxFileName filename = wxFileSelector( wxT("Choose an image file"), wxEmptyString, wxEmptyString, wxEmptyString, wxT("PNG files (*.png)|*.png"), wxFD_OPEN, this ); if (!filename.IsOk() || !filename.MakeRelativeTo(wxGetCwd())) { return; } m_images.push_back(Image(filename.GetFullPath().ToAscii().data())); UpdateImageList(); UpdateSpriteList(); UpdateCanvas(); } void FrameSpriteEditor::OnMenuSelectedImageDelete(wxCommandEvent& event) { if (m_imageIndex == wxNOT_FOUND) { return; } m_images.erase(m_images.begin() + m_imageIndex); UpdateImageList(); UpdateSpriteList(); UpdateCanvas(); } void FrameSpriteEditor::OnMenuSelectedSpriteRename(wxCommandEvent& event) { if (m_imageIndex == wxNOT_FOUND || m_spriteIndex == wxNOT_FOUND) { return; } wxTextEntryDialog dialog(this, wxT("Sprite alias"), wxT("Input a sprite alias"), wxString::FromAscii(m_images[m_imageIndex].sprites[m_spriteIndex].alias.c_str())); if (dialog.ShowModal() != wxID_OK || dialog.GetValue().IsEmpty()) { return; } m_images[m_imageIndex].sprites[m_spriteIndex].alias = dialog.GetValue().ToAscii().data(); UpdateSpriteList(); } void FrameSpriteEditor::OnMenuSelectedSpriteDelete(wxCommandEvent& event) { if (m_imageIndex == wxNOT_FOUND || m_spriteIndex == wxNOT_FOUND) { return; } std::vector& sprites = m_images[m_imageIndex].sprites; sprites.erase(sprites.begin() + m_spriteIndex); UpdateSpriteList(); UpdateCanvas(); } void FrameSpriteEditor::OnMenuSelectedShapeRename(wxCommandEvent& event) { } void FrameSpriteEditor::OnMenuSelectedShapeDelete(wxCommandEvent& event) { if (m_imageIndex == wxNOT_FOUND || m_spriteIndex == wxNOT_FOUND || m_shapeIndex == wxNOT_FOUND) { return; } m_images[m_imageIndex].sprites[m_spriteIndex].shapes[m_shapeIndex].enabled = false; UpdateShapeList(); UpdateCanvas(); } void FrameSpriteEditor::OnImagesRightDown(wxMouseEvent& event) { wxMenu menu; menu.Append(ID_MENU_IMAGE_INSERT, wxT("&Insert image...")); menu.Append(ID_MENU_IMAGE_DELETE, wxT("&Delete image"))->Enable(m_imageIndex != wxNOT_FOUND); PopupMenu(&menu); } void FrameSpriteEditor::OnSpritesRightDown(wxMouseEvent& event) { wxMenu menu; menu.Append(ID_MENU_SPRITE_RENAME, wxT("&Rename sprite..."))->Enable(m_spriteIndex != wxNOT_FOUND); menu.Append(ID_MENU_SPRITE_DELETE, wxT("&Delete sprite"))->Enable(m_spriteIndex != wxNOT_FOUND); PopupMenu(&menu); } void FrameSpriteEditor::OnShapesRightDown(wxMouseEvent& event) { wxMenu menu; menu.Append(ID_MENU_SHAPE_RENAME, wxT("&Rename shape..."))->Enable(m_shapeIndex != wxNOT_FOUND); menu.Append(ID_MENU_SHAPE_DELETE, wxT("&Delete shape"))->Enable(m_shapeIndex != wxNOT_FOUND); PopupMenu(&menu); } void FrameSpriteEditor::OnImagesSelected(wxCommandEvent& event) { m_imageIndex = m_listImages->GetSelection(); if (m_imageIndex == wxNOT_FOUND) { m_currentBitmap = wxNullBitmap; } else { const wxString filename = m_listImages->GetString(m_imageIndex); m_currentBitmap.LoadFile(filename, wxBITMAP_TYPE_PNG); } UpdateSpriteList(); UpdateShapeList(); UpdateCanvas(); } void FrameSpriteEditor::OnSpritesSelected(wxCommandEvent& event) { m_spriteIndex = m_listSprites->GetSelection(); UpdateShapeList(); UpdateCanvas(); } void FrameSpriteEditor::OnShapesSelected(wxCommandEvent& event) { m_shapeIndex = m_listShapes->GetSelection(); UpdateCanvas(); } void FrameSpriteEditor::OnModifiedGrid(wxCommandEvent& event) { long gridSize = m_gridSize; m_textGrid->GetValue().ToLong(&gridSize); m_gridSize = gridSize; } void FrameSpriteEditor::OnClose(wxCloseEvent& event) { Hide(); } void FrameSpriteEditor::UpdateCanvas() { if (m_currentBitmap.IsOk()) { m_panelCanvas->SetMinSize(wxSize(m_currentBitmap.GetWidth() + BITMAP_OFFSET, m_currentBitmap.GetHeight() + BITMAP_OFFSET)); } else { m_panelCanvas->SetMinSize(wxSize(0, 0)); } m_scrolledWindow->FitInside(); m_panelCanvas->Refresh(false); } void FrameSpriteEditor::UpdateImageList() { m_listImages->Clear(); for (std::vector::iterator iter = m_images.begin(); iter != m_images.end(); ++iter) { m_listImages->Append(wxString::FromAscii(iter->filename.c_str())); } GetSizer()->Layout(); } void FrameSpriteEditor::UpdateSpriteList() { m_listSprites->Clear(); if (m_imageIndex != wxNOT_FOUND) { std::vector& sprites = m_images[m_imageIndex].sprites; for (std::vector::iterator iter = sprites.begin(); iter != sprites.end(); ++iter) { m_listSprites->Append(wxString::FromAscii(iter->alias.c_str())); } } GetSizer()->Layout(); } void FrameSpriteEditor::UpdateShapeList() { static const wchar_t* shapeNames[] = { L"COLLISION_SOLID", L"COLLISION_INTERACT" }; m_listShapes->Clear(); if (m_imageIndex != wxNOT_FOUND && m_spriteIndex != wxNOT_FOUND) { const ActorShape* const shapes = m_images[m_imageIndex].sprites[m_spriteIndex].shapes; for (int i = 0; i < ACTOR_SHAPE_TYPE_COUNT; ++i) { if (shapes[i].enabled) { m_listShapes->Append(wxString::Format(wxT("%s: %d"), shapeNames[i], shapes[i].data)); } else { m_listShapes->Append(wxString::Format(wxT("%s: NULL"), shapeNames[i])); } } } GetSizer()->Layout(); } void FrameSpriteEditor::InitializeUi() { m_listImages = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE); m_listImages->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(FrameSpriteEditor::OnImagesRightDown), NULL, this); m_listImages->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler(FrameSpriteEditor::OnImagesSelected), NULL, this); m_listSprites = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE); m_listSprites->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(FrameSpriteEditor::OnSpritesRightDown), NULL, this); m_listSprites->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler(FrameSpriteEditor::OnSpritesSelected), NULL, this); m_listShapes = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE); m_listShapes->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(FrameSpriteEditor::OnShapesRightDown), NULL, this); m_listShapes->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler(FrameSpriteEditor::OnShapesSelected), NULL, this); m_scrolledWindow = new wxScrolledWindow(this); m_scrolledWindow->SetScrollRate(1, 1); m_panelCanvas = new wxPanel(m_scrolledWindow); m_panelCanvas->SetBackgroundColour(wxColour(0x00, 0x00, 0x00)); m_panelCanvas->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(FrameSpriteEditor::OnCanvasLeftDown), NULL, this); m_panelCanvas->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(FrameSpriteEditor::OnCanvasLeftUp), NULL, this); m_panelCanvas->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(FrameSpriteEditor::OnCanvasRightDown), NULL, this); m_panelCanvas->Connect(wxEVT_MOTION, wxMouseEventHandler(FrameSpriteEditor::OnCanvasMotion), NULL, this); m_panelCanvas->Connect(wxEVT_PAINT, wxPaintEventHandler(FrameSpriteEditor::OnCanvasPaint), NULL, this); m_textGrid = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); m_textGrid->Connect(wxEVT_COMMAND_TEXT_ENTER, wxTextEventHandler(FrameSpriteEditor::OnModifiedGrid), NULL, this); m_textGrid->SetValue(wxString::Format(wxT("%d"), m_gridSize)); wxBoxSizer* sizerScroll = new wxBoxSizer(wxVERTICAL); sizerScroll->Add(m_panelCanvas, 1, wxEXPAND); m_scrolledWindow->SetSizer(sizerScroll); wxBoxSizer* sizerOptions = new wxBoxSizer(wxHORIZONTAL); sizerOptions->Add(new wxStaticText(this, wxID_ANY, wxT("Grid")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); sizerOptions->Add(m_textGrid, 0, wxALL, 5); wxBoxSizer* sizerAssets = new wxBoxSizer(wxVERTICAL); sizerAssets->Add(new wxStaticText(this, wxID_ANY, wxT("Images")), 0, wxALL, 5); sizerAssets->Add(m_listImages, 1, wxALL | wxEXPAND, 5); sizerAssets->Add(new wxStaticText(this, wxID_ANY, wxT("Sprites")), 0, wxALL, 5); sizerAssets->Add(m_listSprites, 1, wxALL | wxEXPAND, 5); sizerAssets->Add(new wxStaticText(this, wxID_ANY, wxT("Shapes")), 0, wxALL, 5); sizerAssets->Add(m_listShapes, 1, wxALL | wxEXPAND, 5); wxBoxSizer* sizerCanvas = new wxBoxSizer(wxVERTICAL); sizerCanvas->Add(m_scrolledWindow, 1, wxEXPAND | wxALL, 5); sizerCanvas->Add(sizerOptions, 0, wxALIGN_RIGHT, 5); wxBoxSizer* sizerLayout = new wxBoxSizer(wxHORIZONTAL); sizerLayout->Add(sizerAssets, 0, wxEXPAND); sizerLayout->Add(sizerCanvas, 1, wxEXPAND); SetSizer(sizerLayout); } void FrameSpriteEditor::InitializeMenuBar() { wxMenu* menuFile = new wxMenu(); menuFile->Append(ID_MENU_FILE_NEW, wxT("&New\tCtrl+N")); menuFile->Append(ID_MENU_FILE_OPEN, wxT("&Open\tCtrl+O")); menuFile->Append(ID_MENU_FILE_SAVE, wxT("&Save\tCtrl+S")); menuFile->Append(ID_MENU_FILE_SAVE_AS, wxT("Save &As...\tCtrl+Shift+S")); menuFile->AppendSeparator(); menuFile->Append(ID_MENU_FILE_EXIT, wxT("E&xit\tCtrl+Q")); wxMenuBar* menuBar = new wxMenuBar(); menuBar->Append(menuFile, wxT("&File")); SetMenuBar(menuBar); } Vector2i FrameSpriteEditor::AdjustPosition(const Vector2i& position) const { const Vector2i mod(position.x % m_gridSize, position.y % m_gridSize); Vector2i positionAdj = position - mod; const Vector2i sign(positionAdj.x >= 0 ? 1 : -1, positionAdj.y >= 0 ? 1 : -1); if (abs(mod.x) > m_gridSize / 2) { positionAdj.x += m_gridSize * sign.x; } if (abs(mod.y) > m_gridSize / 2) { positionAdj.y += m_gridSize * sign.y; } return positionAdj - Vector2i(BITMAP_OFFSET, BITMAP_OFFSET); }