/** GDevelop - Tile Map Extension Copyright (c) 2014 Victor Levasseur (victorlevasseur52@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "TileEditor.h" #include #include #include #include "GDCore/CommonTools.h" #include "GDCore/IDE/CommonBitmapManager.h" TileEditor::TileEditor(wxWindow* parent) : TileEditorBase(parent), m_tileset(NULL), m_currentTile(0), m_predefinedShapesMenu(new wxMenu()), m_xOffset(0.f), m_yOffset(0.f), m_polygonHelper() { m_tilePreviewPanel->SetBackgroundStyle(wxBG_STYLE_PAINT); UpdateScrollbars(); //Create the predefined shape menu m_predefinedShapesMenu->Append(RECTANGLE_SHAPE_TOOL_ID, _("Rectangle Shape")); m_predefinedShapesMenu->AppendSeparator(); m_predefinedShapesMenu->Append(TRIANGLE_TL_SHAPE_TOOL_ID, _("Triangle Shape (top-left)")); m_predefinedShapesMenu->Append(TRIANGLE_TR_SHAPE_TOOL_ID, _("Triangle Shape (top-right)")); m_predefinedShapesMenu->Append(TRIANGLE_BR_SHAPE_TOOL_ID, _("Triangle Shape (bottom-right)")); m_predefinedShapesMenu->Append(TRIANGLE_BL_SHAPE_TOOL_ID, _("Triangle Shape (bottom-left)")); m_predefinedShapesMenu->AppendSeparator(); m_predefinedShapesMenu->Append(SEMIRECT_T_SHAPE_TOOL_ID, _("Half-rectangle (top)")); m_predefinedShapesMenu->Append(SEMIRECT_R_SHAPE_TOOL_ID, _("Half-rectangle (right)")); m_predefinedShapesMenu->Append(SEMIRECT_B_SHAPE_TOOL_ID, _("Half-rectangle (bottom)")); m_predefinedShapesMenu->Append(SEMIRECT_L_SHAPE_TOOL_ID, _("Half-rectangle (left)")); Connect(RECTANGLE_SHAPE_TOOL_ID, SEMIRECT_L_SHAPE_TOOL_ID, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TileEditor::OnPredefinedShapeMenuItemClicked), NULL, this); } TileEditor::~TileEditor() { } void TileEditor::SetTileSet(TileSet *tileset) { m_tileset = tileset; UpdateScrollbars(); m_tilePreviewPanel->Refresh(); //Update the tools according to the selected tile TileSelectionEvent event(TILE_SELECTION_CHANGED, -1, m_currentTile); OnTileSetSelectionChanged(event); } void TileEditor::OnTileSetSelectionChanged(TileSelectionEvent &event) { if(!m_tileset || m_tileset->IsDirty()) return; m_currentTile = event.GetSelectedTile(); m_mainToolbar->ToggleTool(COLLIDABLE_TOOL_ID, m_tileset->GetTileHitbox(m_currentTile).collidable); UpdateScrollbars(); m_tilePreviewPanel->Refresh(); } void TileEditor::OnPreviewErase(wxEraseEvent& event) { } void TileEditor::UpdateScrollbars() { if(!m_tileset || m_tileset->IsDirty()) //If no tileset, stop rendering here return; //Compute the virtual size and the default scroll position to have a centered tile. wxBitmap tileBitmap = m_tileset->GetTileBitmap(m_currentTile); int virtualWidth = std::max(m_tilePreviewPanel->GetClientSize().GetWidth(), tileBitmap.GetWidth()); int virtualHeight = std::max(m_tilePreviewPanel->GetClientSize().GetHeight(), tileBitmap.GetHeight()); m_tilePreviewPanel->SetVirtualSize(virtualWidth, virtualHeight); m_tilePreviewPanel->Scroll(virtualWidth/2 - m_tilePreviewPanel->GetClientSize().GetWidth()/2, virtualHeight/2 - m_tilePreviewPanel->GetClientSize().GetHeight()/2); } wxPoint TileEditor::GetRealPosition(wxPoint absolutePos) { wxPoint realPoint(m_tilePreviewPanel->CalcUnscrolledPosition(absolutePos).x - m_xOffset, m_tilePreviewPanel->CalcUnscrolledPosition(absolutePos).y - m_yOffset); return realPoint; } void TileEditor::OnPreviewPaint(wxPaintEvent& event) { //Prepare the render wxAutoBufferedPaintDC dc(m_tilePreviewPanel); m_tilePreviewPanel->DoPrepareDC(dc); wxPoint minPos = m_tilePreviewPanel->GetViewStart(); int width, height; m_tilePreviewPanel->GetClientSize(&width, &height); wxPoint maxPos = minPos + wxPoint(width, height); //Draw the background dc.SetBrush(gd::CommonBitmapManager::Get()->transparentBg); dc.DrawRectangle(minPos.x, minPos.y, width, height); if(!m_tileset || m_tileset->IsDirty()) //If no tileset, stop rendering here return; //Draw the tile and compute the drawing offset wxBitmap tileBitmap = m_tileset->GetTileBitmap(m_currentTile); m_xOffset = width/2 - tileBitmap.GetWidth()/2; m_yOffset = height/2 - tileBitmap.GetHeight()/2; dc.DrawBitmap(tileBitmap, m_xOffset, m_yOffset); //Draw the hitbox std::vector polygonList(1, m_tileset->GetTileHitbox(m_currentTile).hitbox); m_polygonHelper.OnPaint(polygonList, dc, wxPoint(m_xOffset, m_yOffset)); //Show a warning if the polygon isn't convex if(!m_tileset->GetTileHitbox(m_currentTile).hitbox.IsConvex()) { dc.DrawBitmap(wxBitmap("res/warning.png", wxBITMAP_TYPE_PNG), 5, 5); dc.SetPen(wxPen(wxColour(0,0,0))); dc.DrawText(_("This polygon must be convex, it's not the case."), 25, 5); } } void TileEditor::OnCollidableToolToggled(wxCommandEvent& event) { m_tileset->GetTileHitbox(m_currentTile).collidable = event.IsChecked(); } void TileEditor::OnPredefinedShapeToolClicked(wxCommandEvent& event) { PopupMenu(m_predefinedShapesMenu); } void TileEditor::OnPredefinedShapeMenuItemClicked(wxCommandEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; //Set the predefined shapes as hitbox switch(event.GetId()) { case RECTANGLE_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Rectangle(m_tileset->tileSize); break; case TRIANGLE_TL_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Triangle(m_tileset->tileSize, TileHitbox::TopLeft); break; case TRIANGLE_TR_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Triangle(m_tileset->tileSize, TileHitbox::TopRight); break; case TRIANGLE_BR_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Triangle(m_tileset->tileSize, TileHitbox::BottomRight); break; case TRIANGLE_BL_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Triangle(m_tileset->tileSize, TileHitbox::BottomLeft); break; case SEMIRECT_T_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Rectangle(sf::Vector2f(m_tileset->tileSize.x, m_tileset->tileSize.y/2.f)); break; case SEMIRECT_R_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Rectangle(sf::Vector2f(m_tileset->tileSize.x/2.f, m_tileset->tileSize.y)); m_tileset->GetTileHitbox(m_currentTile).hitbox.Move(m_tileset->tileSize.x/2.f, 0); break; case SEMIRECT_B_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Rectangle(sf::Vector2f(m_tileset->tileSize.x, m_tileset->tileSize.y/2.f)); m_tileset->GetTileHitbox(m_currentTile).hitbox.Move(0, m_tileset->tileSize.y/2.f); break; case SEMIRECT_L_SHAPE_TOOL_ID: m_tileset->GetTileHitbox(m_currentTile) = TileHitbox::Rectangle(sf::Vector2f(m_tileset->tileSize.x/2.f, m_tileset->tileSize.y)); break; } //Update the tools according to the properties' changes TileSelectionEvent tileEvent(TILE_SELECTION_CHANGED, -1, m_currentTile); OnTileSetSelectionChanged(tileEvent); } void TileEditor::OnAddPointToolClicked(wxCommandEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; Polygon2d &mask = m_tileset->GetTileHitbox(m_currentTile).hitbox; int selectedPoint = m_polygonHelper.GetSelectedPoint(); if(selectedPoint >= mask.vertices.size() || selectedPoint < 0) { if(mask.vertices.size() <= 2) return; else selectedPoint = mask.vertices.size() - 1; } int nextToSelectedPoint = ( (selectedPoint == (mask.vertices.size() - 1) ) ? 0 : selectedPoint + 1 ); sf::Vector2f newPoint = mask.vertices[selectedPoint] + mask.vertices[nextToSelectedPoint]; newPoint.x /= 2.f; newPoint.y /= 2.f; mask.vertices.insert(mask.vertices.begin() + selectedPoint + 1, newPoint); m_tilePreviewPanel->Refresh(); } void TileEditor::OnEditPointToolClicked(wxCommandEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; Polygon2d &mask = m_tileset->GetTileHitbox(m_currentTile).hitbox; int selectedPoint = m_polygonHelper.GetSelectedPoint(); if(selectedPoint >= mask.vertices.size() || selectedPoint < 0) return; std::string x_str = gd::ToString(wxGetTextFromUser(_("Enter the X position of the point ( regarding the tile )."), _("X position"),gd::ToString(mask.vertices[selectedPoint].x))); std::string y_str = gd::ToString(wxGetTextFromUser(_("Enter the Y position of the point ( regarding the tile )."), _("Y position"),gd::ToString(mask.vertices[selectedPoint].y))); mask.vertices[selectedPoint].x = gd::ToInt(x_str); mask.vertices[selectedPoint].y = gd::ToInt(y_str); m_tilePreviewPanel->Refresh(); } void TileEditor::OnRemovePointToolClicked(wxCommandEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; Polygon2d &mask = m_tileset->GetTileHitbox(m_currentTile).hitbox; if(mask.vertices.size() <= 3) return; int selectedPoint = m_polygonHelper.GetSelectedPoint(); if(selectedPoint >= mask.vertices.size() || selectedPoint < 0) return; mask.vertices.erase(mask.vertices.begin() + selectedPoint); m_tilePreviewPanel->Refresh(); } void TileEditor::OnPreviewLeftDown(wxMouseEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; event.SetX(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).x); event.SetY(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).y); std::vector polygonList(1, m_tileset->GetTileHitbox(m_currentTile).hitbox); m_polygonHelper.OnMouseLeftDown(polygonList, event, wxPoint(m_xOffset, m_yOffset)); m_tileset->GetTileHitbox(m_currentTile).hitbox = polygonList[0]; m_tilePreviewPanel->Refresh(); } void TileEditor::OnPreviewLeftUp(wxMouseEvent& event) { event.SetX(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).x); event.SetY(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).y); m_polygonHelper.OnMouseLeftUp(event); m_tilePreviewPanel->Refresh(); } void TileEditor::OnPreviewMotion(wxMouseEvent& event) { if(!m_tileset || m_tileset->IsDirty()) return; event.SetX(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).x); event.SetY(m_tilePreviewPanel->CalcUnscrolledPosition(wxPoint(event.GetX(), event.GetY())).y); std::vector polygonList(1, m_tileset->GetTileHitbox(m_currentTile).hitbox); m_polygonHelper.OnMouseMove(polygonList, event, wxPoint(m_xOffset, m_yOffset), 0.f, 0.f, m_tileset->tileSize.x, m_tileset->tileSize.y); m_tileset->GetTileHitbox(m_currentTile).hitbox = polygonList[0]; m_tilePreviewPanel->Refresh(); }