ddnet/src/game/editor/popups.cpp

2252 lines
72 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/color.h>
#include <engine/console.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/input.h>
#include <engine/keys.h>
2022-05-29 16:33:38 +00:00
#include <engine/shared/config.h>
#include <engine/storage.h>
#include <limits>
#include <game/client/ui_scrollregion.h>
2011-04-19 08:34:51 +00:00
#include "editor.h"
2008-01-17 23:09:49 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2008-01-17 23:09:49 +00:00
static int s_NewMapButton = 0;
static int s_SaveButton = 0;
static int s_SaveAsButton = 0;
static int s_SaveCopyButton = 0;
static int s_OpenButton = 0;
static int s_OpenCurrentMapButton = 0;
static int s_AppendButton = 0;
static int s_MapInfoButton = 0;
static int s_ExitButton = 0;
CUIRect Slot;
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_NewMapButton, "New", 0, &Slot, 0, "Creates a new map (ctrl+n)"))
{
if(pEditor->HasUnsavedData())
{
pEditor->m_PopupEventType = POPEVENT_NEW;
pEditor->m_PopupEventActivated = true;
}
else
{
pEditor->Reset();
pEditor->m_aFileName[0] = 0;
}
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(10.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_OpenButton, "Load", 0, &Slot, 0, "Opens a map for editing (ctrl+l)"))
{
if(pEditor->HasUnsavedData())
{
pEditor->m_PopupEventType = POPEVENT_LOAD;
pEditor->m_PopupEventActivated = true;
}
else
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", pEditor->CallbackOpenMap, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, "Load Current Map", 0, &Slot, 0, "Opens the current in game map for editing (ctrl+alt+l)"))
{
if(pEditor->HasUnsavedData())
{
pEditor->m_PopupEventType = POPEVENT_LOADCURRENT;
pEditor->m_PopupEventActivated = true;
}
else
{
pEditor->LoadCurrentMap();
}
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(10.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_AppendButton, "Append", 0, &Slot, 0, "Opens a map and adds everything from that map to the current one (ctrl+a)"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Append map", "Append", "maps", "", pEditor->CallbackAppendMap, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(10.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_SaveButton, "Save", 0, &Slot, 0, "Saves the current map (ctrl+s)"))
{
if(pEditor->m_aFileName[0] && pEditor->m_ValidSaveFilename)
{
str_copy(pEditor->m_aFileSaveName, pEditor->m_aFileName);
pEditor->m_PopupEventType = POPEVENT_SAVE;
pEditor->m_PopupEventActivated = true;
}
else
pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveMap, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_SaveAsButton, "Save As", 0, &Slot, 0, "Saves the current map under a new name (ctrl+shift+s)"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveMap, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_SaveCopyButton, "Save Copy", 0, &Slot, 0, "Saves a copy of the current map under a new name (ctrl+shift+alt+s)"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveCopyMap, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(10.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_MapInfoButton, "Map details", 0, &Slot, 0, "Adjust the map details of the current map"))
{
const CUIRect *pScreen = pEditor->UI()->Screen();
pEditor->m_Map.m_MapInfoTmp.Copy(pEditor->m_Map.m_MapInfo);
static SPopupMenuId s_PopupMapInfoId;
constexpr float PopupWidth = 400.0f;
constexpr float PopupHeight = 170.0f;
pEditor->UI()->DoPopupMenu(&s_PopupMapInfoId, pScreen->w / 2.0f - PopupWidth / 2.0f, pScreen->h / 2.0f - PopupHeight / 2.0f, PopupWidth, PopupHeight, pEditor, PopupMapInfo);
pEditor->UI()->SetActiveItem(nullptr);
return CUI::POPUP_CLOSE_CURRENT;
}
2023-04-01 21:17:37 +00:00
View.HSplitTop(10.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_ExitButton, "Exit", 0, &Slot, 0, "Exits from the editor"))
{
if(pEditor->HasUnsavedData())
{
pEditor->m_PopupEventType = POPEVENT_EXIT;
pEditor->m_PopupEventActivated = true;
}
else
g_Config.m_ClEditor = 0;
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupMenuTools(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CUIRect Slot;
View.HSplitTop(12.0f, &Slot, &View);
static int s_RemoveUnusedEnvelopesButton = 0;
static CUI::SConfirmPopupContext s_ConfirmPopupContext;
if(pEditor->DoButton_MenuItem(&s_RemoveUnusedEnvelopesButton, "Remove unused envelopes", 0, &Slot, 0, "Removes all unused envelopes from the map"))
{
s_ConfirmPopupContext.Reset();
s_ConfirmPopupContext.YesNoButtons();
str_copy(s_ConfirmPopupContext.m_aMessage, "Are you sure that you want to remove all unused envelopes from this map?");
pEditor->UI()->ShowPopupConfirm(Slot.x + Slot.w, Slot.y, &s_ConfirmPopupContext);
}
if(s_ConfirmPopupContext.m_Result == CUI::SConfirmPopupContext::CONFIRMED)
pEditor->RemoveUnusedEnvelopes();
if(s_ConfirmPopupContext.m_Result != CUI::SConfirmPopupContext::UNSET)
{
s_ConfirmPopupContext.Reset();
return CUI::POPUP_CLOSE_CURRENT;
}
static int s_BorderButton = 0;
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
2023-05-01 16:02:53 +00:00
if(pEditor->DoButton_MenuItem(&s_BorderButton, "Place Border", 0, &Slot, 0, "Place tiles in a 2-tile wide border at the edges of the layer"))
{
CLayerTiles *pT = (CLayerTiles *)pEditor->GetSelectedLayerType(0, LAYERTYPE_TILES);
if(pT && !pT->m_Tele && !pT->m_Speedup && !pT->m_Switch && !pT->m_Front && !pT->m_Tune)
{
pEditor->m_PopupEventType = POPEVENT_PLACE_BORDER_TILES;
pEditor->m_PopupEventActivated = true;
}
else
{
static CUI::SMessagePopupContext s_MessagePopupContext;
s_MessagePopupContext.DefaultColor(pEditor->m_pTextRender);
str_copy(s_MessagePopupContext.m_aMessage, "No tile layer selected");
pEditor->UI()->ShowPopupMessage(Slot.x, Slot.y + Slot.h, &s_MessagePopupContext);
}
}
static int s_GotoButton = 0;
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_GotoButton, "Goto XY", 0, &Slot, 0, "Go to a specified coordinate point on the map"))
{
static SPopupMenuId s_PopupGotoId;
pEditor->UI()->DoPopupMenu(&s_PopupGotoId, Slot.x, Slot.y + Slot.h, 120, 52, pEditor, PopupGoto);
}
return CUI::POPUP_KEEP_OPEN;
}
static int EntitiesListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser)
{
CEditor *pEditor = (CEditor *)pUser;
if(!IsDir && str_endswith(pName, ".png"))
{
std::string Name = pName;
pEditor->m_vSelectEntitiesFiles.push_back(Name.substr(0, Name.length() - 4));
}
return 0;
}
CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CUIRect Slot;
View.HSplitTop(12.0f, &Slot, &View);
static int s_EntitiesButtonID = 0;
2023-05-01 16:02:53 +00:00
char aButtonText[64];
str_format(aButtonText, sizeof(aButtonText), "Entities: %s", pEditor->m_SelectEntitiesImage.c_str());
if(pEditor->DoButton_MenuItem(&s_EntitiesButtonID, aButtonText, 0, &Slot, 0, "Choose game layer entities image for different gametypes"))
{
pEditor->m_vSelectEntitiesFiles.clear();
pEditor->Storage()->ListDirectory(IStorage::TYPE_ALL, "editor/entities", EntitiesListdirCallback, pEditor);
std::sort(pEditor->m_vSelectEntitiesFiles.begin(), pEditor->m_vSelectEntitiesFiles.end());
static SPopupMenuId s_PopupEntitiesId;
pEditor->UI()->DoPopupMenu(&s_PopupEntitiesId, Slot.x, Slot.y + Slot.h, 250, pEditor->m_vSelectEntitiesFiles.size() * 14.0f + 10.0f, pEditor, PopupEntities);
}
2023-05-01 20:44:03 +00:00
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect No, Yes;
Selector.VSplitMid(&No, &Yes);
pEditor->UI()->DoLabel(&Label, "Brush coloring", 10.0f, TEXTALIGN_ML);
2023-05-01 20:44:03 +00:00
static int s_ButtonNo = 0;
static int s_ButtonYes = 0;
if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !pEditor->m_BrushColorEnabled, &No, 0, "Disable brush coloring"))
{
pEditor->m_BrushColorEnabled = false;
}
if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", pEditor->m_BrushColorEnabled, &Yes, 0, "Enable brush coloring"))
{
pEditor->m_BrushColorEnabled = true;
}
}
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect No, Yes;
Selector.VSplitMid(&No, &Yes);
pEditor->UI()->DoLabel(&Label, "Allow unused", 10.0f, TEXTALIGN_ML);
static int s_ButtonNo = 0;
static int s_ButtonYes = 0;
if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !pEditor->m_AllowPlaceUnusedTiles, &No, 0, "[ctrl+u] Disallow placing unused tiles"))
{
pEditor->m_AllowPlaceUnusedTiles = false;
}
if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", pEditor->m_AllowPlaceUnusedTiles, &Yes, 0, "[ctrl+u] Allow placing unused tiles"))
{
pEditor->m_AllowPlaceUnusedTiles = true;
}
}
2023-05-01 20:44:03 +00:00
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect Off, Dec, Hex;
Selector.VSplitLeft(Selector.w / 3.0f, &Off, &Selector);
Selector.VSplitMid(&Dec, &Hex);
pEditor->UI()->DoLabel(&Label, "Show Info", 10.0f, TEXTALIGN_ML);
static int s_ButtonOff = 0;
static int s_ButtonDec = 0;
static int s_ButtonHex = 0;
2023-05-22 18:20:45 +00:00
if(pEditor->DoButton_ButtonDec(&s_ButtonOff, "Off", pEditor->m_ShowTileInfo == SHOW_TILE_OFF, &Off, 0, "Do not show tile information"))
2023-05-01 20:44:03 +00:00
{
2023-05-22 18:20:45 +00:00
pEditor->m_ShowTileInfo = SHOW_TILE_OFF;
2023-05-01 20:44:03 +00:00
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
}
2023-05-23 18:16:42 +00:00
if(pEditor->DoButton_Ex(&s_ButtonDec, "Dec", pEditor->m_ShowTileInfo == SHOW_TILE_DECIMAL, &Dec, 0, "[ctrl+i] Show tile information", IGraphics::CORNER_NONE))
2023-05-01 20:44:03 +00:00
{
2023-05-22 12:46:25 +00:00
pEditor->m_ShowTileInfo = SHOW_TILE_DECIMAL;
2023-05-01 20:44:03 +00:00
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
}
2023-05-22 18:20:45 +00:00
if(pEditor->DoButton_ButtonInc(&s_ButtonHex, "Hex", pEditor->m_ShowTileInfo == SHOW_TILE_HEXADECIMAL, &Hex, 0, "[ctrl+shift+i] Show tile information in hexadecimal"))
2023-05-01 20:44:03 +00:00
{
2023-05-22 18:20:45 +00:00
pEditor->m_ShowTileInfo = SHOW_TILE_HEXADECIMAL;
2023-05-01 20:44:03 +00:00
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
}
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2008-01-17 23:09:49 +00:00
// remove group button
2010-05-29 07:25:38 +00:00
CUIRect Button;
View.HSplitBottom(12.0f, &View, &Button);
static int s_DeleteButton = 0;
// don't allow deletion of game group
if(pEditor->m_Map.m_pGameGroup != pEditor->GetSelectedGroup())
2008-01-17 23:09:49 +00:00
{
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, 0, "Delete group"))
{
pEditor->m_Map.DeleteGroup(pEditor->m_SelectedGroup);
pEditor->m_SelectedGroup = maximum(0, pEditor->m_SelectedGroup - 1);
return CUI::POPUP_CLOSE_CURRENT;
}
}
else
{
2014-12-01 19:35:30 +00:00
if(pEditor->DoButton_Editor(&s_DeleteButton, "Clean up game tiles", 0, &Button, 0, "Removes game tiles that aren't based on a layer"))
{
// gather all tile layers
2022-06-15 17:34:41 +00:00
std::vector<CLayerTiles *> vpLayers;
for(auto &pLayer : pEditor->m_Map.m_pGameGroup->m_vpLayers)
{
if(pLayer != pEditor->m_Map.m_pGameLayer && pLayer->m_Type == LAYERTYPE_TILES)
2022-06-15 17:34:41 +00:00
vpLayers.push_back(static_cast<CLayerTiles *>(pLayer));
}
// search for unneeded game tiles
CLayerTiles *pGameLayer = pEditor->m_Map.m_pGameLayer;
for(int y = 0; y < pGameLayer->m_Height; ++y)
2023-04-01 21:18:27 +00:00
{
for(int x = 0; x < pGameLayer->m_Width; ++x)
{
if(pGameLayer->m_pTiles[y * pGameLayer->m_Width + x].m_Index > static_cast<unsigned char>(TILE_NOHOOK))
continue;
bool Found = false;
2022-06-15 17:34:41 +00:00
for(const auto &pLayer : vpLayers)
{
if(x < pLayer->m_Width && y < pLayer->m_Height && pLayer->m_pTiles[y * pLayer->m_Width + x].m_Index)
{
Found = true;
break;
}
}
if(!Found)
{
pGameLayer->m_pTiles[y * pGameLayer->m_Width + x].m_Index = TILE_AIR;
pEditor->m_Map.m_Modified = true;
}
}
2023-04-01 21:18:27 +00:00
}
return CUI::POPUP_CLOSE_CURRENT;
}
2008-01-17 23:09:49 +00:00
}
if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTeleLayer)
{
// new tele layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button);
2019-11-22 11:28:34 +00:00
static int s_NewTeleLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewTeleLayerButton, "Add tele layer", 0, &Button, 0, "Creates a new tele layer"))
{
CLayer *pTeleLayer = new CLayerTele(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeTeleLayer(pTeleLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTeleLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Brush.Clear();
return CUI::POPUP_CLOSE_CURRENT;
}
}
if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSpeedupLayer)
{
// new speedup layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button);
2019-11-22 11:28:34 +00:00
static int s_NewSpeedupLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewSpeedupLayerButton, "Add speedup layer", 0, &Button, 0, "Creates a new speedup layer"))
{
CLayer *pSpeedupLayer = new CLayerSpeedup(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeSpeedupLayer(pSpeedupLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSpeedupLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Brush.Clear();
return CUI::POPUP_CLOSE_CURRENT;
}
}
2015-07-09 00:08:14 +00:00
if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTuneLayer)
{
// new tune layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewTuneLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewTuneLayerButton, "Add tune layer", 0, &Button, 0, "Creates a new tuning layer"))
{
CLayer *pTuneLayer = new CLayerTune(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeTuneLayer(pTuneLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTuneLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Brush.Clear();
return CUI::POPUP_CLOSE_CURRENT;
}
}
if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pFrontLayer)
{
2019-11-22 11:28:34 +00:00
// new front layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewFrontLayerButton = 0;
2014-12-01 19:35:30 +00:00
if(pEditor->DoButton_Editor(&s_NewFrontLayerButton, "Add front layer", 0, &Button, 0, "Creates a new item layer"))
{
CLayer *pFrontLayer = new CLayerFront(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeFrontLayer(pFrontLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pFrontLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Brush.Clear();
return CUI::POPUP_CLOSE_CURRENT;
}
}
if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSwitchLayer)
{
// new Switch layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewSwitchLayerButton = 0;
2014-12-01 19:35:30 +00:00
if(pEditor->DoButton_Editor(&s_NewSwitchLayerButton, "Add switch layer", 0, &Button, 0, "Creates a new switch layer"))
{
CLayer *pSwitchLayer = new CLayerSwitch(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeSwitchLayer(pSwitchLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSwitchLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Brush.Clear();
return CUI::POPUP_CLOSE_CURRENT;
}
}
2014-10-08 15:33:06 +00:00
// new quad layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
2010-05-29 07:25:38 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewQuadLayerButton = 0;
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_NewQuadLayerButton, "Add quads layer", 0, &Button, 0, "Creates a new quad layer"))
2008-01-17 23:09:49 +00:00
{
CLayer *pQuadLayer = new CLayerQuads;
pQuadLayer->m_pEditor = pEditor;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pQuadLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
return CUI::POPUP_CLOSE_CURRENT;
2008-01-17 23:09:49 +00:00
}
2014-10-08 15:33:06 +00:00
// new tile layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
2010-05-29 07:25:38 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewTileLayerButton = 0;
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_NewTileLayerButton, "Add tile layer", 0, &Button, 0, "Creates a new tile layer"))
2008-01-17 23:09:49 +00:00
{
CLayer *pTileLayer = new CLayerTiles(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pTileLayer->m_pEditor = pEditor;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTileLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
return CUI::POPUP_CLOSE_CURRENT;
2008-01-17 23:09:49 +00:00
}
2014-10-08 15:33:06 +00:00
// new sound layer
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
2014-10-08 15:33:06 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewSoundLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewSoundLayerButton, "Add sound layer", 0, &Button, 0, "Creates a new sound layer"))
{
CLayer *pSoundLayer = new CLayerSounds;
pSoundLayer->m_pEditor = pEditor;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSoundLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
return CUI::POPUP_CLOSE_CURRENT;
2014-10-08 15:33:06 +00:00
}
2011-07-12 01:14:46 +00:00
// group name
if(!pEditor->GetSelectedGroup()->m_GameGroup)
{
2023-04-01 21:18:27 +00:00
View.HSplitBottom(5.0f, &View, nullptr);
2011-07-12 01:14:46 +00:00
View.HSplitBottom(12.0f, &View, &Button);
pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, TEXTALIGN_ML);
Button.VSplitLeft(40.0f, nullptr, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_NameInput;
s_NameInput.SetBuffer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName));
if(pEditor->DoEditBox(&s_NameInput, &Button, 10.0f))
2011-07-20 20:18:31 +00:00
pEditor->m_Map.m_Modified = true;
2011-07-12 01:14:46 +00:00
}
2008-01-17 23:09:49 +00:00
enum
{
PROP_ORDER = 0,
2008-01-17 23:09:49 +00:00
PROP_POS_X,
PROP_POS_Y,
PROP_PARA_X,
PROP_PARA_Y,
PROP_CUSTOM_ZOOM,
PROP_PARA_ZOOM,
2008-03-29 11:44:03 +00:00
PROP_USE_CLIPPING,
PROP_CLIP_X,
PROP_CLIP_Y,
PROP_CLIP_W,
PROP_CLIP_H,
2008-01-17 23:09:49 +00:00
NUM_PROPS,
};
2010-05-29 07:25:38 +00:00
CProperty aProps[] = {
{"Order", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1},
{"Pos X", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para Y", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Custom Zoom", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom, PROPTYPE_BOOL, 0, 1},
{"Para Zoom", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Use Clipping", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping, PROPTYPE_BOOL, 0, 1},
{"Clip X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Clip Y", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Clip W", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Clip H", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH, PROPTYPE_INT_SCROLL, 0, 1000000},
{nullptr},
2008-01-17 23:09:49 +00:00
};
2023-04-01 21:18:27 +00:00
// cut the properties that aren't needed
2010-05-29 07:25:38 +00:00
if(pEditor->GetSelectedGroup()->m_GameGroup)
aProps[PROP_POS_X].m_pName = nullptr;
2023-04-01 21:18:27 +00:00
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
2010-05-29 07:25:38 +00:00
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-01 21:18:27 +00:00
}
2010-05-29 07:25:38 +00:00
if(Prop == PROP_ORDER)
2023-04-01 21:18:27 +00:00
{
2010-05-29 07:25:38 +00:00
pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(pEditor->m_SelectedGroup, NewVal);
2023-04-01 21:18:27 +00:00
}
2008-01-17 23:09:49 +00:00
// these can not be changed on the game group
2010-05-29 07:25:38 +00:00
if(!pEditor->GetSelectedGroup()->m_GameGroup)
2008-01-17 23:09:49 +00:00
{
if(Prop == PROP_PARA_X)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_PARA_Y)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_CUSTOM_ZOOM)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_PARA_ZOOM)
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom = 1;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom = NewVal;
}
else if(Prop == PROP_POS_X)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_POS_Y)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_USE_CLIPPING)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_CLIP_X)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_CLIP_Y)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_CLIP_W)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal;
2023-04-01 21:18:27 +00:00
}
else if(Prop == PROP_CLIP_H)
2023-04-01 21:18:27 +00:00
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal;
2023-04-01 21:18:27 +00:00
}
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->OnEdited();
2008-01-17 23:09:49 +00:00
}
return CUI::POPUP_KEEP_OPEN;
2008-01-17 23:09:49 +00:00
}
CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
SLayerPopupContext *pPopup = (SLayerPopupContext *)pContext;
CEditor *pEditor = pPopup->m_pEditor;
2020-02-28 15:25:27 +00:00
CLayerGroup *pCurrentGroup = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup];
CLayer *pCurrentLayer = pEditor->GetSelectedLayer(0);
if(pPopup->m_vpLayers.size() > 1)
{
return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers);
}
const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer();
// delete button
if(pEditor->m_Map.m_pGameLayer != pCurrentLayer) // entities layers except the game layer can be deleted
{
CUIRect DeleteButton;
View.HSplitBottom(12.0f, &View, &DeleteButton);
static int s_DeleteButton = 0;
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &DeleteButton, 0, "Deletes the layer"))
{
if(pCurrentLayer == pEditor->m_Map.m_pFrontLayer)
pEditor->m_Map.m_pFrontLayer = nullptr;
if(pCurrentLayer == pEditor->m_Map.m_pTeleLayer)
pEditor->m_Map.m_pTeleLayer = nullptr;
if(pCurrentLayer == pEditor->m_Map.m_pSpeedupLayer)
pEditor->m_Map.m_pSpeedupLayer = nullptr;
if(pCurrentLayer == pEditor->m_Map.m_pSwitchLayer)
pEditor->m_Map.m_pSwitchLayer = nullptr;
if(pCurrentLayer == pEditor->m_Map.m_pTuneLayer)
pEditor->m_Map.m_pTuneLayer = nullptr;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DeleteLayer(pEditor->m_vSelectedLayers[0]);
return CUI::POPUP_CLOSE_CURRENT;
}
}
// duplicate button
if(!EntitiesLayer) // entities layers cannot be duplicated
2008-01-17 23:09:49 +00:00
{
CUIRect DuplicateButton;
View.HSplitBottom(4.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &DuplicateButton);
static int s_DuplicationButton = 0;
if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DuplicateButton, 0, "Duplicates the layer"))
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]);
return CUI::POPUP_CLOSE_CURRENT;
}
2008-01-17 23:09:49 +00:00
}
2011-07-12 01:14:46 +00:00
// layer name
if(!EntitiesLayer) // name cannot be changed for entities layers
2011-07-12 01:14:46 +00:00
{
CUIRect Label, EditBox;
View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Label);
Label.VSplitLeft(40.0f, &Label, &EditBox);
pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, TEXTALIGN_ML);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_NameInput;
s_NameInput.SetBuffer(pCurrentLayer->m_aName, sizeof(pCurrentLayer->m_aName));
if(pEditor->DoEditBox(&s_NameInput, &EditBox, 10.0f))
2011-07-20 20:18:31 +00:00
pEditor->m_Map.m_Modified = true;
2011-07-12 01:14:46 +00:00
}
// spacing if any button was rendered
if(!EntitiesLayer || pEditor->m_Map.m_pGameLayer != pCurrentLayer)
View.HSplitBottom(10.0f, &View, nullptr);
2008-01-17 23:09:49 +00:00
enum
{
PROP_GROUP = 0,
2008-01-17 23:09:49 +00:00
PROP_ORDER,
PROP_HQ,
2008-01-17 23:09:49 +00:00
NUM_PROPS,
};
2010-05-29 07:25:38 +00:00
CProperty aProps[] = {
{"Group", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1},
{"Order", pEditor->m_vSelectedLayers[0], PROPTYPE_INT_STEP, 0, (int)pCurrentGroup->m_vpLayers.size() - 1},
{"Detail", pCurrentLayer->m_Flags & LAYERFLAG_DETAIL, PROPTYPE_BOOL, 0, 1},
{nullptr},
2008-01-17 23:09:49 +00:00
};
2010-05-29 07:25:38 +00:00
// don't use Group and Detail from the selection if this is an entities layer
if(EntitiesLayer)
2010-05-29 07:25:38 +00:00
{
aProps[0].m_Type = PROPTYPE_NULL;
aProps[2].m_Type = PROPTYPE_NULL;
}
2010-05-29 07:25:38 +00:00
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
2023-04-01 21:19:04 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-01 21:19:04 +00:00
}
2010-05-29 07:25:38 +00:00
if(Prop == PROP_ORDER)
2023-04-01 21:19:04 +00:00
{
pEditor->SelectLayer(pCurrentGroup->SwapLayers(pEditor->m_vSelectedLayers[0], NewVal));
2023-04-01 21:19:04 +00:00
}
else if(Prop == PROP_GROUP)
2008-01-17 23:09:49 +00:00
{
if(NewVal >= 0 && (size_t)NewVal < pEditor->m_Map.m_vpGroups.size())
2008-01-17 23:09:49 +00:00
{
auto Position = std::find(pCurrentGroup->m_vpLayers.begin(), pCurrentGroup->m_vpLayers.end(), pCurrentLayer);
if(Position != pCurrentGroup->m_vpLayers.end())
pCurrentGroup->m_vpLayers.erase(Position);
pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.push_back(pCurrentLayer);
2010-05-29 07:25:38 +00:00
pEditor->m_SelectedGroup = NewVal;
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.size() - 1);
2008-01-17 23:09:49 +00:00
}
}
else if(Prop == PROP_HQ)
{
2010-05-29 07:25:38 +00:00
pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL;
if(NewVal)
pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL;
}
2010-05-29 07:25:38 +00:00
return pCurrentLayer->RenderProperties(&View);
2008-01-17 23:09:49 +00:00
}
CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2022-06-15 17:34:41 +00:00
std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads();
CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
2023-04-02 09:43:17 +00:00
CLayerQuads *pLayer = static_cast<CLayerQuads *>(pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS));
2008-01-17 23:09:49 +00:00
2010-05-29 07:25:38 +00:00
CUIRect Button;
2008-03-02 15:59:52 +00:00
// delete button
2010-05-29 07:25:38 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_DeleteButton = 0;
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, 0, "Deletes the current quad"))
2008-03-02 15:59:52 +00:00
{
2010-05-29 07:25:38 +00:00
if(pLayer)
2008-03-02 15:59:52 +00:00
{
pEditor->m_Map.m_Modified = true;
2018-08-13 09:11:56 +00:00
pEditor->DeleteSelectedQuads();
2008-03-02 15:59:52 +00:00
}
return CUI::POPUP_CLOSE_CURRENT;
2008-03-02 15:59:52 +00:00
}
// aspect ratio button
2023-04-02 09:43:17 +00:00
View.HSplitBottom(10.0f, &View, nullptr);
2011-03-29 10:08:46 +00:00
View.HSplitBottom(12.0f, &View, &Button);
if(pLayer && pLayer->m_Image >= 0 && (size_t)pLayer->m_Image < pEditor->m_Map.m_vpImages.size())
{
static int s_AspectRatioButton = 0;
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, 0, "Resizes the current Quad based on the aspect ratio of the image"))
{
2022-06-15 17:34:41 +00:00
for(auto &pQuad : vpQuads)
{
int Top = pQuad->m_aPoints[0].y;
int Left = pQuad->m_aPoints[0].x;
int Right = pQuad->m_aPoints[0].x;
2018-08-13 09:11:56 +00:00
for(int k = 1; k < 4; k++)
{
if(pQuad->m_aPoints[k].y < Top)
Top = pQuad->m_aPoints[k].y;
if(pQuad->m_aPoints[k].x < Left)
Left = pQuad->m_aPoints[k].x;
if(pQuad->m_aPoints[k].x > Right)
Right = pQuad->m_aPoints[k].x;
2018-08-13 09:11:56 +00:00
}
2023-04-02 09:43:17 +00:00
const int Height = (Right - Left) * pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_Height / pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_Width;
2018-08-13 09:11:56 +00:00
pQuad->m_aPoints[0].x = Left;
pQuad->m_aPoints[0].y = Top;
pQuad->m_aPoints[1].x = Right;
pQuad->m_aPoints[1].y = Top;
pQuad->m_aPoints[2].x = Left;
pQuad->m_aPoints[2].y = Top + Height;
pQuad->m_aPoints[3].x = Right;
pQuad->m_aPoints[3].y = Top + Height;
2018-08-13 09:11:56 +00:00
pEditor->m_Map.m_Modified = true;
}
return CUI::POPUP_CLOSE_CURRENT;
}
2011-03-29 10:08:46 +00:00
}
// align button
2023-04-02 09:43:17 +00:00
View.HSplitBottom(6.0f, &View, nullptr);
2011-03-29 10:08:46 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_AlignButton = 0;
if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, 0, "Aligns coordinates of the quad points"))
{
2022-06-15 17:34:41 +00:00
for(auto &pQuad : vpQuads)
2011-03-29 10:08:46 +00:00
{
2018-08-13 09:11:56 +00:00
for(int k = 1; k < 4; k++)
{
pQuad->m_aPoints[k].x = 1000.0f * (pQuad->m_aPoints[k].x / 1000);
pQuad->m_aPoints[k].y = 1000.0f * (pQuad->m_aPoints[k].y / 1000);
2018-08-13 09:11:56 +00:00
}
pEditor->m_Map.m_Modified = true;
2011-03-29 10:08:46 +00:00
}
return CUI::POPUP_CLOSE_CURRENT;
}
// square button
2023-04-02 09:43:17 +00:00
View.HSplitBottom(6.0f, &View, nullptr);
2010-05-29 07:25:38 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_Button = 0;
2011-03-20 16:04:03 +00:00
if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, 0, "Squares the current quad"))
2008-01-17 23:09:49 +00:00
{
2022-06-15 17:34:41 +00:00
for(auto &pQuad : vpQuads)
2008-01-17 23:09:49 +00:00
{
int Top = pQuad->m_aPoints[0].y;
int Left = pQuad->m_aPoints[0].x;
int Bottom = pQuad->m_aPoints[0].y;
int Right = pQuad->m_aPoints[0].x;
2018-08-13 09:11:56 +00:00
for(int k = 1; k < 4; k++)
{
if(pQuad->m_aPoints[k].y < Top)
Top = pQuad->m_aPoints[k].y;
if(pQuad->m_aPoints[k].x < Left)
Left = pQuad->m_aPoints[k].x;
if(pQuad->m_aPoints[k].y > Bottom)
Bottom = pQuad->m_aPoints[k].y;
if(pQuad->m_aPoints[k].x > Right)
Right = pQuad->m_aPoints[k].x;
2018-08-13 09:11:56 +00:00
}
pQuad->m_aPoints[0].x = Left;
pQuad->m_aPoints[0].y = Top;
pQuad->m_aPoints[1].x = Right;
pQuad->m_aPoints[1].y = Top;
pQuad->m_aPoints[2].x = Left;
pQuad->m_aPoints[2].y = Bottom;
pQuad->m_aPoints[3].x = Right;
pQuad->m_aPoints[3].y = Bottom;
2018-08-13 09:11:56 +00:00
pEditor->m_Map.m_Modified = true;
}
return CUI::POPUP_CLOSE_CURRENT;
2008-01-17 23:09:49 +00:00
}
2022-02-26 17:49:06 +00:00
// slice button
2023-04-02 09:43:17 +00:00
View.HSplitBottom(6.0f, &View, nullptr);
2022-02-26 17:49:06 +00:00
View.HSplitBottom(12.0f, &View, &Button);
static int s_SliceButton = 0;
if(pEditor->DoButton_Editor(&s_SliceButton, "Slice", 0, &Button, 0, "Enables quad knife mode"))
{
pEditor->m_QuadKnifeCount = 0;
pEditor->m_QuadKnifeActive = true;
return CUI::POPUP_CLOSE_CURRENT;
2022-02-26 17:49:06 +00:00
}
2008-01-17 23:09:49 +00:00
enum
{
PROP_ORDER = 0,
PROP_POS_X,
2011-03-29 10:08:46 +00:00
PROP_POS_Y,
PROP_POS_ENV,
2008-01-17 23:09:49 +00:00
PROP_POS_ENV_OFFSET,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
NUM_PROPS,
};
2023-04-02 09:43:17 +00:00
const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0;
2010-05-29 07:25:38 +00:00
CProperty aProps[] = {
{"Order", pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], PROPTYPE_INT_STEP, 0, NumQuads},
{"Pos X", fx2i(pCurrentQuad->m_aPoints[4].x), PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", fx2i(pCurrentQuad->m_aPoints[4].y), PROPTYPE_INT_SCROLL, -1000000, 1000000},
2020-09-22 12:47:21 +00:00
{"Pos. Env", pCurrentQuad->m_PosEnv + 1, PROPTYPE_ENVELOPE, 0, 0},
2018-08-13 09:11:56 +00:00
{"Pos. TO", pCurrentQuad->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
2020-09-22 12:47:21 +00:00
{"Color Env", pCurrentQuad->m_ColorEnv + 1, PROPTYPE_ENVELOPE, 0, 0},
2018-08-13 09:11:56 +00:00
{"Color TO", pCurrentQuad->m_ColorEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{nullptr},
2008-01-17 23:09:49 +00:00
};
2010-05-29 07:25:38 +00:00
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
2023-04-02 09:43:17 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-02 09:43:17 +00:00
}
2023-04-02 09:43:17 +00:00
const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x;
const float OffsetY = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].y;
2018-08-13 09:11:56 +00:00
if(Prop == PROP_ORDER && pLayer)
{
2023-04-02 09:43:17 +00:00
const int QuadIndex = pLayer->SwapQuads(pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], NewVal);
pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex;
}
2022-06-15 17:34:41 +00:00
for(auto &pQuad : vpQuads)
{
2018-08-13 09:11:56 +00:00
if(Prop == PROP_POS_X)
{
for(auto &Point : pQuad->m_aPoints)
2020-10-26 14:14:07 +00:00
Point.x += OffsetX;
}
2023-04-02 09:43:17 +00:00
else if(Prop == PROP_POS_Y)
{
for(auto &Point : pQuad->m_aPoints)
2020-10-26 14:14:07 +00:00
Point.y += OffsetY;
2018-08-13 09:11:56 +00:00
}
2023-04-02 09:43:17 +00:00
else if(Prop == PROP_POS_ENV)
2018-08-13 09:11:56 +00:00
{
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1;
if(StepDirection != 0)
2018-08-13 09:11:56 +00:00
{
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
2023-04-02 09:43:17 +00:00
{
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 3)
2018-08-13 09:11:56 +00:00
{
pQuad->m_PosEnv = Index;
2018-08-13 09:11:56 +00:00
break;
}
2023-04-02 09:43:17 +00:00
}
2018-08-13 09:11:56 +00:00
}
}
2023-04-02 09:43:17 +00:00
else if(Prop == PROP_POS_ENV_OFFSET)
{
pQuad->m_PosEnvOffset = NewVal;
2023-04-02 09:43:17 +00:00
}
else if(Prop == PROP_COLOR_ENV)
2018-08-13 09:11:56 +00:00
{
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1;
if(StepDirection != 0)
2018-08-13 09:11:56 +00:00
{
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
2023-04-02 09:43:17 +00:00
{
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 4)
2018-08-13 09:11:56 +00:00
{
pQuad->m_ColorEnv = Index;
2018-08-13 09:11:56 +00:00
break;
}
2023-04-02 09:43:17 +00:00
}
2018-08-13 09:11:56 +00:00
}
}
2023-04-02 09:43:17 +00:00
else if(Prop == PROP_COLOR_ENV_OFFSET)
{
pQuad->m_ColorEnvOffset = NewVal;
2023-04-02 09:43:17 +00:00
}
}
return CUI::POPUP_KEEP_OPEN;
2008-01-17 23:09:49 +00:00
}
CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CSoundSource *pSource = pEditor->GetSelectedSource();
CUIRect Button;
// delete button
View.HSplitBottom(12.0f, &View, &Button);
static int s_DeleteButton = 0;
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, 0, "Deletes the current source"))
{
CLayerSounds *pLayer = (CLayerSounds *)pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS);
if(pLayer)
{
pEditor->m_Map.m_Modified = true;
pLayer->m_vSources.erase(pLayer->m_vSources.begin() + pEditor->m_SelectedSource);
pEditor->m_SelectedSource--;
}
return CUI::POPUP_CLOSE_CURRENT;
}
// Sound shape button
CUIRect ShapeButton;
View.HSplitBottom(3.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &ShapeButton);
2023-04-01 21:19:30 +00:00
static const char *s_apShapeNames[CSoundShape::NUM_SHAPES] = {
"Rectangle",
"Circle"};
pSource->m_Shape.m_Type = pSource->m_Shape.m_Type % CSoundShape::NUM_SHAPES; // prevent out of array errors
2014-11-30 10:33:44 +00:00
2023-04-01 21:19:30 +00:00
static int s_ShapeTypeButton = 0;
if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_apShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, 0, "Change shape"))
{
pSource->m_Shape.m_Type = (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES;
// set default values
switch(pSource->m_Shape.m_Type)
{
case CSoundShape::SHAPE_CIRCLE:
{
pSource->m_Shape.m_Circle.m_Radius = 1000.0f;
break;
}
case CSoundShape::SHAPE_RECTANGLE:
{
pSource->m_Shape.m_Rectangle.m_Width = f2fx(1000.0f);
pSource->m_Shape.m_Rectangle.m_Height = f2fx(800.0f);
break;
}
}
}
2015-07-09 00:08:14 +00:00
enum
{
PROP_POS_X = 0,
PROP_POS_Y,
PROP_LOOP,
PROP_PAN,
PROP_TIME_DELAY,
PROP_FALLOFF,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
2014-10-11 14:05:36 +00:00
PROP_SOUND_ENV,
PROP_SOUND_ENV_OFFSET,
NUM_PROPS,
};
CProperty aProps[] = {
2020-09-22 13:42:20 +00:00
{"Pos X", pSource->m_Position.x / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", pSource->m_Position.y / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Loop", pSource->m_Loop, PROPTYPE_BOOL, 0, 1},
{"Pan", pSource->m_Pan, PROPTYPE_BOOL, 0, 1},
{"Delay", pSource->m_TimeDelay, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Falloff", pSource->m_Falloff, PROPTYPE_INT_SCROLL, 0, 255},
2020-09-22 13:42:20 +00:00
{"Pos. Env", pSource->m_PosEnv + 1, PROPTYPE_ENVELOPE, 0, 0},
{"Pos. TO", pSource->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
2020-09-22 13:42:20 +00:00
{"Sound Env", pSource->m_SoundEnv + 1, PROPTYPE_ENVELOPE, 0, 0},
2020-09-22 12:50:04 +00:00
{"Sound. TO", pSource->m_SoundEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{nullptr},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
2023-04-01 21:19:30 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-01 21:19:30 +00:00
}
if(Prop == PROP_POS_X)
2023-04-01 21:19:30 +00:00
{
pSource->m_Position.x = NewVal * 1000;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_POS_Y)
{
pSource->m_Position.y = NewVal * 1000;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_LOOP)
{
pSource->m_Loop = NewVal;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_PAN)
{
pSource->m_Pan = NewVal;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_TIME_DELAY)
{
pSource->m_TimeDelay = NewVal;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_FALLOFF)
{
pSource->m_Falloff = NewVal;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_POS_ENV)
{
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
2023-04-01 21:19:30 +00:00
const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
{
2023-04-01 21:19:30 +00:00
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 3)
{
pSource->m_PosEnv = Index;
break;
}
}
}
2023-04-01 21:19:30 +00:00
else if(Prop == PROP_POS_ENV_OFFSET)
{
pSource->m_PosEnvOffset = NewVal;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_SOUND_ENV)
2014-10-11 14:05:36 +00:00
{
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
2023-04-01 21:19:30 +00:00
const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
2014-10-11 14:05:36 +00:00
{
2023-04-01 21:19:30 +00:00
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 1)
{
pSource->m_SoundEnv = Index;
break;
}
2014-10-11 14:05:36 +00:00
}
}
2023-04-01 21:19:30 +00:00
else if(Prop == PROP_SOUND_ENV_OFFSET)
{
pSource->m_SoundEnvOffset = NewVal;
2023-04-01 21:19:30 +00:00
}
// source shape properties
switch(pSource->m_Shape.m_Type)
{
case CSoundShape::SHAPE_CIRCLE:
{
enum
{
PROP_CIRCLE_RADIUS = 0,
NUM_CIRCLE_PROPS,
};
2015-07-09 00:08:14 +00:00
CProperty aCircleProps[] = {
{"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT_SCROLL, 0, 1000000},
{nullptr},
};
static int s_aCircleIds[NUM_CIRCLE_PROPS] = {0};
NewVal = 0;
Prop = pEditor->DoProperties(&View, aCircleProps, s_aCircleIds, &NewVal);
if(Prop != -1)
2023-04-01 21:19:30 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-01 21:19:30 +00:00
}
if(Prop == PROP_CIRCLE_RADIUS)
2023-04-01 21:19:30 +00:00
{
pSource->m_Shape.m_Circle.m_Radius = NewVal;
2023-04-01 21:19:30 +00:00
}
break;
}
2015-07-09 00:08:14 +00:00
case CSoundShape::SHAPE_RECTANGLE:
{
enum
{
PROP_RECTANGLE_WIDTH = 0,
PROP_RECTANGLE_HEIGHT,
NUM_RECTANGLE_PROPS,
};
2015-07-09 00:08:14 +00:00
CProperty aRectangleProps[] = {
{"Width", pSource->m_Shape.m_Rectangle.m_Width / 1024, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Height", pSource->m_Shape.m_Rectangle.m_Height / 1024, PROPTYPE_INT_SCROLL, 0, 1000000},
{nullptr},
};
static int s_aRectangleIds[NUM_RECTANGLE_PROPS] = {0};
NewVal = 0;
Prop = pEditor->DoProperties(&View, aRectangleProps, s_aRectangleIds, &NewVal);
if(Prop != -1)
2023-04-01 21:19:30 +00:00
{
pEditor->m_Map.m_Modified = true;
2023-04-01 21:19:30 +00:00
}
if(Prop == PROP_RECTANGLE_WIDTH)
2023-04-01 21:19:30 +00:00
{
pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024;
2023-04-01 21:19:30 +00:00
}
else if(Prop == PROP_RECTANGLE_HEIGHT)
{
pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024;
2023-04-01 21:19:30 +00:00
}
break;
}
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2022-06-15 17:34:41 +00:00
std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads();
CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
2008-01-17 23:09:49 +00:00
enum
{
PROP_POS_X = 0,
2011-03-29 10:08:46 +00:00
PROP_POS_Y,
PROP_COLOR,
PROP_TEX_U,
PROP_TEX_V,
2008-01-17 23:09:49 +00:00
NUM_PROPS,
};
2010-05-29 07:25:38 +00:00
int Color = 0;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].r << 24;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].g << 16;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].b << 8;
2018-08-13 09:11:56 +00:00
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].a;
2023-04-01 21:19:48 +00:00
const int X = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x);
const int Y = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y);
const int TextureU = fx2f(pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].x) * 1024;
const int TextureV = fx2f(pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].y) * 1024;
2010-05-29 07:25:38 +00:00
CProperty aProps[] = {
2023-04-01 21:19:48 +00:00
{"Pos X", X, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", Y, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Color", Color, PROPTYPE_COLOR, 0, 0},
2023-04-01 21:19:48 +00:00
{"Tex U", TextureU, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Tex V", TextureV, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{nullptr},
2008-01-17 23:09:49 +00:00
};
2010-05-29 07:25:38 +00:00
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
2011-03-29 10:08:46 +00:00
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
2023-04-01 21:19:48 +00:00
{
2011-03-29 10:08:46 +00:00
pEditor->m_Map.m_Modified = true;
2023-04-01 21:19:48 +00:00
}
2011-03-29 10:08:46 +00:00
2022-06-15 17:34:41 +00:00
for(auto &pQuad : vpQuads)
2008-01-17 23:09:49 +00:00
{
2018-08-13 09:11:56 +00:00
if(Prop == PROP_POS_X)
{
for(int v = 0; v < 4; v++)
2020-09-22 12:47:21 +00:00
if(pEditor->m_SelectedPoints & (1 << v))
2023-04-01 21:19:48 +00:00
pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X);
2018-08-13 09:11:56 +00:00
}
2023-04-01 21:19:48 +00:00
else if(Prop == PROP_POS_Y)
2008-01-17 23:09:49 +00:00
{
2018-08-13 09:11:56 +00:00
for(int v = 0; v < 4; v++)
2020-09-22 12:47:21 +00:00
if(pEditor->m_SelectedPoints & (1 << v))
2023-04-01 21:19:48 +00:00
pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y);
2018-08-13 09:11:56 +00:00
}
2023-04-01 21:19:48 +00:00
else if(Prop == PROP_COLOR)
2018-08-13 09:11:56 +00:00
{
for(int v = 0; v < 4; v++)
2008-01-17 23:09:49 +00:00
{
if(pEditor->m_SelectedPoints & (1 << v))
2018-08-13 09:11:56 +00:00
{
pQuad->m_aColors[v].r = (NewVal >> 24) & 0xff;
pQuad->m_aColors[v].g = (NewVal >> 16) & 0xff;
pQuad->m_aColors[v].b = (NewVal >> 8) & 0xff;
pQuad->m_aColors[v].a = NewVal & 0xff;
2018-08-13 09:11:56 +00:00
}
2008-01-17 23:09:49 +00:00
}
}
2023-04-01 21:19:48 +00:00
else if(Prop == PROP_TEX_U)
{
for(int v = 0; v < 4; v++)
2020-09-22 12:47:21 +00:00
if(pEditor->m_SelectedPoints & (1 << v))
2023-04-01 21:19:48 +00:00
pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f);
}
2023-04-01 21:19:48 +00:00
else if(Prop == PROP_TEX_V)
{
for(int v = 0; v < 4; v++)
2020-09-22 12:47:21 +00:00
if(pEditor->m_SelectedPoints & (1 << v))
2023-04-01 21:19:48 +00:00
pQuad->m_aTexcoords[v].y = f2fx(fx2f(pQuad->m_aTexcoords[v].y) + (NewVal - TextureV) / 1024.0f);
}
2008-01-17 23:09:49 +00:00
}
return CUI::POPUP_KEEP_OPEN;
2008-01-17 23:09:49 +00:00
}
static int gs_ModifyIndexDeletedIndex;
static void ModifyIndexDeleted(int *pIndex)
{
if(*pIndex == gs_ModifyIndexDeletedIndex)
*pIndex = -1;
else if(*pIndex > gs_ModifyIndexDeletedIndex)
*pIndex = *pIndex - 1;
}
CUI::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
static int s_ReaddButton = 0;
static int s_ReplaceButton = 0;
static int s_RemoveButton = 0;
CUIRect Slot;
View.HSplitTop(12.0f, &Slot, &View);
CEditorImage *pImg = pEditor->m_Map.m_vpImages[pEditor->m_SelectedImage];
static int s_ExternalButton = 0;
if(pImg->m_External)
{
if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, 0, "Embeds the image into the map file."))
{
pImg->m_External = 0;
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
}
else if(pEditor->IsVanillaImage(pImg->m_aName))
{
if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, 0, "Removes the image from the map file."))
{
pImg->m_External = 1;
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
}
static CUI::SSelectionPopupContext s_SelectionPopupContext;
static CScrollRegion s_SelectionPopupScrollRegion;
s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
if(pEditor->DoButton_MenuItem(&s_ReaddButton, "Readd", 0, &Slot, 0, "Reloads the image from the mapres folder"))
{
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "%s.png", pImg->m_aName);
s_SelectionPopupContext.Reset();
std::set<std::string> EntriesSet;
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet);
for(const auto &Entry : EntriesSet)
s_SelectionPopupContext.m_vEntries.push_back(Entry);
if(s_SelectionPopupContext.m_vEntries.empty())
{
pEditor->ShowFileDialogError("Error: could not find image '%s' in the mapres folder.", aFilename);
}
else if(s_SelectionPopupContext.m_vEntries.size() == 1)
{
s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
}
else
{
str_copy(s_SelectionPopupContext.m_aMessage, "Select the wanted image:");
pEditor->UI()->ShowPopupSelection(pEditor->UI()->MouseX(), pEditor->UI()->MouseY(), &s_SelectionPopupContext);
}
}
if(s_SelectionPopupContext.m_pSelection != nullptr)
{
2023-04-01 21:20:11 +00:00
const bool WasExternal = pImg->m_External;
const bool Result = pEditor->ReplaceImage(s_SelectionPopupContext.m_pSelection->c_str(), IStorage::TYPE_ALL, false);
pImg->m_External = WasExternal;
s_SelectionPopupContext.Reset();
return Result ? CUI::POPUP_CLOSE_CURRENT : CUI::POPUP_KEEP_OPEN;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the image with a new one"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Replace Image", "Replace", "mapres", "", ReplaceImageCallback, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, 0, "Removes the image from the map"))
{
delete pImg;
pEditor->m_Map.m_vpImages.erase(pEditor->m_Map.m_vpImages.begin() + pEditor->m_SelectedImage);
gs_ModifyIndexDeletedIndex = pEditor->m_SelectedImage;
pEditor->m_Map.ModifyImageIndex(ModifyIndexDeleted);
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
static int s_ReaddButton = 0;
static int s_ReplaceButton = 0;
static int s_RemoveButton = 0;
CUIRect Slot;
View.HSplitTop(12.0f, &Slot, &View);
CEditorSound *pSound = pEditor->m_Map.m_vpSounds[pEditor->m_SelectedSound];
static CUI::SSelectionPopupContext s_SelectionPopupContext;
static CScrollRegion s_SelectionPopupScrollRegion;
s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
if(pEditor->DoButton_MenuItem(&s_ReaddButton, "Readd", 0, &Slot, 0, "Reloads the sound from the mapres folder"))
{
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "%s.opus", pSound->m_aName);
s_SelectionPopupContext.Reset();
std::set<std::string> EntriesSet;
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet);
for(const auto &Entry : EntriesSet)
s_SelectionPopupContext.m_vEntries.push_back(Entry);
if(s_SelectionPopupContext.m_vEntries.empty())
{
pEditor->ShowFileDialogError("Error: could not find sound '%s' in the mapres folder.", aFilename);
}
else if(s_SelectionPopupContext.m_vEntries.size() == 1)
{
s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
}
else
{
str_copy(s_SelectionPopupContext.m_aMessage, "Select the wanted sound:");
pEditor->UI()->ShowPopupSelection(pEditor->UI()->MouseX(), pEditor->UI()->MouseY(), &s_SelectionPopupContext);
}
}
if(s_SelectionPopupContext.m_pSelection != nullptr)
{
const bool Result = pEditor->ReplaceSound(s_SelectionPopupContext.m_pSelection->c_str(), IStorage::TYPE_ALL, false);
s_SelectionPopupContext.Reset();
return Result ? CUI::POPUP_CLOSE_CURRENT : CUI::POPUP_KEEP_OPEN;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the sound with a new one"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Replace sound", "Replace", "mapres", "", ReplaceSoundCallback, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(5.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, 0, "Removes the sound from the map"))
{
delete pSound;
pEditor->m_Map.m_vpSounds.erase(pEditor->m_Map.m_vpSounds.begin() + pEditor->m_SelectedSound);
gs_ModifyIndexDeletedIndex = pEditor->m_SelectedSound;
pEditor->m_Map.ModifySoundIndex(ModifyIndexDeleted);
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupNewFolder(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CUIRect Label, ButtonBar, Button;
View.Margin(10.0f, &View);
View.HSplitBottom(20.0f, &View, &ButtonBar);
// title
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Create new folder", 20.0f, TEXTALIGN_MC);
View.HSplitTop(10.0f, nullptr, &View);
// folder name
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, TEXTALIGN_ML);
Label.VSplitLeft(50.0f, nullptr, &Button);
Button.HMargin(2.0f, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
pEditor->DoEditBox(&pEditor->m_FileDialogNewFolderNameInput, &Button, 12.0f);
// button bar
ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar);
static int s_CancelButton = 0;
if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, 0, nullptr))
return CUI::POPUP_CLOSE_CURRENT;
ButtonBar.VSplitRight(110.0f, &ButtonBar, &Button);
static int s_CreateButton = 0;
if(pEditor->DoButton_Editor(&s_CreateButton, "Create", 0, &Button, 0, nullptr) || (Active && pEditor->UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
{
// create the folder
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
if(!pEditor->m_FileDialogNewFolderNameInput.IsEmpty())
{
char aBuf[IO_MAX_PATH_LENGTH];
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
str_format(aBuf, sizeof(aBuf), "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_FileDialogNewFolderNameInput.GetString());
if(pEditor->Storage()->CreateFolder(aBuf, IStorage::TYPE_SAVE))
{
pEditor->FilelistPopulate(IStorage::TYPE_SAVE);
return CUI::POPUP_CLOSE_CURRENT;
}
else
{
pEditor->ShowFileDialogError("Failed to create the folder '%s'.", aBuf);
}
}
}
return CUI::POPUP_KEEP_OPEN;
}
2008-01-17 23:09:49 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View, bool Active)
2011-07-12 21:31:39 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2011-07-12 21:31:39 +00:00
CUIRect Label, ButtonBar, Button;
View.Margin(10.0f, &View);
2011-07-12 21:31:39 +00:00
View.HSplitBottom(20.0f, &View, &ButtonBar);
// title
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Map details", 20.0f, TEXTALIGN_MC);
View.HSplitTop(10.0f, nullptr, &View);
2011-07-12 21:31:39 +00:00
// author box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Author:", 10.0f, TEXTALIGN_ML);
Label.VSplitLeft(60.0f, nullptr, &Button);
Button.HMargin(3.0f, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_AuthorInput;
s_AuthorInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aAuthor, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aAuthor));
pEditor->DoEditBox(&s_AuthorInput, &Button, 10.0f);
2011-07-12 21:31:39 +00:00
// version box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Version:", 10.0f, TEXTALIGN_ML);
Label.VSplitLeft(60.0f, nullptr, &Button);
Button.HMargin(3.0f, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_VersionInput;
s_VersionInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aVersion, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aVersion));
pEditor->DoEditBox(&s_VersionInput, &Button, 10.0f);
2011-07-12 21:31:39 +00:00
// credits box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Credits:", 10.0f, TEXTALIGN_ML);
Label.VSplitLeft(60.0f, nullptr, &Button);
Button.HMargin(3.0f, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_CreditsInput;
s_CreditsInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aCredits, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aCredits));
pEditor->DoEditBox(&s_CreditsInput, &Button, 10.0f);
2011-07-12 21:31:39 +00:00
// license box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "License:", 10.0f, TEXTALIGN_ML);
Label.VSplitLeft(60.0f, nullptr, &Button);
Button.HMargin(3.0f, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_LicenseInput;
s_LicenseInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aLicense, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aLicense));
pEditor->DoEditBox(&s_LicenseInput, &Button, 10.0f);
2011-07-12 21:31:39 +00:00
// button bar
ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar);
static int s_CancelButton = 0;
if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Label, 0, nullptr))
return CUI::POPUP_CLOSE_CURRENT;
2011-07-12 21:31:39 +00:00
ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label);
static int s_ConfirmButton = 0;
if(pEditor->DoButton_Editor(&s_ConfirmButton, "Confirm", 0, &Label, 0, nullptr) || (Active && pEditor->UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
{
pEditor->m_Map.m_MapInfo.Copy(pEditor->m_Map.m_MapInfoTmp);
return CUI::POPUP_CLOSE_CURRENT;
}
2011-07-12 21:31:39 +00:00
return CUI::POPUP_KEEP_OPEN;
}
2008-01-17 23:09:49 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
const char *pTitle;
const char *pMessage;
if(pEditor->m_PopupEventType == POPEVENT_EXIT)
{
pTitle = "Exit the editor";
pMessage = "The map contains unsaved data, you might want to save it before you exit the editor.\n\nContinue anyway?";
}
else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT)
{
pTitle = "Load map";
pMessage = "The map contains unsaved data, you might want to save it before you load a new map.\n\nContinue anyway?";
}
else if(pEditor->m_PopupEventType == POPEVENT_NEW)
{
pTitle = "New map";
pMessage = "The map contains unsaved data, you might want to save it before you create a new map.\n\nContinue anyway?";
}
else if(pEditor->m_PopupEventType == POPEVENT_SAVE)
{
pTitle = "Save map";
pMessage = "The file already exists.\n\nDo you want to overwrite the map?";
}
else if(pEditor->m_PopupEventType == POPEVENT_LARGELAYER)
{
pTitle = "Large layer";
pMessage = "You are trying to set the height or width of a layer to more than 1000 tiles. This is actually possible, but only rarely necessary. It may cause the editor to work slower and will result in a larger file size as well as higher memory usage for client and server.";
}
else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES)
{
pTitle = "Unused tiles disabled";
2023-05-01 20:44:03 +00:00
pMessage = "Unused tiles can't be placed by default because they could get a use later and then destroy your map.\n\nActivate the 'Allow Unused' setting to be able to place every tile.";
}
2020-06-20 23:14:36 +00:00
else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16)
{
pTitle = "Image width/height";
pMessage = "The width or height of this image is not divisible by 16. This is required for images used in tile layers.";
}
else if(pEditor->m_PopupEventType == POPEVENT_IMAGE_MAX)
{
pTitle = "Max images";
pMessage = "The client only allows a maximum of 64 images.";
}
else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
{
pTitle = "Place border tiles";
pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?";
}
else
{
dbg_assert(false, "m_PopupEventType invalid");
return CUI::POPUP_CLOSE_CURRENT;
}
CUIRect Label, ButtonBar, Button;
View.Margin(10.0f, &View);
View.HSplitBottom(20.0f, &View, &ButtonBar);
// title
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, pTitle, 20.0f, TEXTALIGN_MC);
// message
2022-03-11 16:34:48 +00:00
SLabelProperties Props;
Props.m_MaxWidth = View.w;
pEditor->UI()->DoLabel(&View, pMessage, 10.0f, TEXTALIGN_ML, Props);
// button bar
ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar);
if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX)
{
static int s_CancelButton = 0;
if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, 0, nullptr))
{
pEditor->m_PopupEventWasActivated = false;
return CUI::POPUP_CLOSE_CURRENT;
}
}
ButtonBar.VSplitRight(110.0f, &ButtonBar, &Button);
static int s_ConfirmButton = 0;
if(pEditor->DoButton_Editor(&s_ConfirmButton, "Confirm", 0, &Button, 0, nullptr) || (Active && pEditor->UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
{
if(pEditor->m_PopupEventType == POPEVENT_EXIT)
2023-04-01 21:20:11 +00:00
{
g_Config.m_ClEditor = 0;
2023-04-01 21:20:11 +00:00
}
else if(pEditor->m_PopupEventType == POPEVENT_LOAD)
2023-04-01 21:20:11 +00:00
{
2022-01-22 16:34:23 +00:00
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", CEditor::CallbackOpenMap, pEditor);
2023-04-01 21:20:11 +00:00
}
2016-08-23 01:08:36 +00:00
else if(pEditor->m_PopupEventType == POPEVENT_LOADCURRENT)
2023-04-01 21:20:11 +00:00
{
2016-08-23 01:08:36 +00:00
pEditor->LoadCurrentMap();
2023-04-01 21:20:11 +00:00
}
else if(pEditor->m_PopupEventType == POPEVENT_NEW)
{
pEditor->Reset();
pEditor->m_aFileName[0] = 0;
}
else if(pEditor->m_PopupEventType == POPEVENT_SAVE)
{
CallbackSaveMap(pEditor->m_aFileSaveName, IStorage::TYPE_SAVE, pEditor);
return CUI::POPUP_CLOSE_CURRENT;
}
else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
{
pEditor->PlaceBorderTiles();
}
pEditor->m_PopupEventWasActivated = false;
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}
2008-01-17 23:09:49 +00:00
2010-05-29 07:25:38 +00:00
static int g_SelectImageSelected = -100;
static int g_SelectImageCurrent = -100;
2008-01-17 23:09:49 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupSelectImage(void *pContext, CUIRect View, bool Active)
2008-01-17 23:09:49 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2010-05-29 07:25:38 +00:00
CUIRect ButtonBar, ImageView;
View.VSplitLeft(150.0f, &ButtonBar, &View);
2010-05-29 07:25:38 +00:00
View.Margin(10.0f, &ImageView);
2010-05-29 07:25:38 +00:00
int ShowImage = g_SelectImageCurrent;
const float ButtonHeight = 12.0f;
const float ButtonMargin = 2.0f;
static CScrollRegion s_ScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = 10.0f;
ScrollParams.m_ScrollbarMargin = 3.0f;
ScrollParams.m_ScrollUnit = (ButtonHeight + ButtonMargin) * 5;
s_ScrollRegion.Begin(&ButtonBar, &ScrollOffset, &ScrollParams);
ButtonBar.y += ScrollOffset.y;
for(int i = -1; i < (int)pEditor->m_Map.m_vpImages.size(); i++)
2008-01-17 23:09:49 +00:00
{
2010-05-29 07:25:38 +00:00
CUIRect Button;
ButtonBar.HSplitTop(ButtonMargin, nullptr, &ButtonBar);
ButtonBar.HSplitTop(ButtonHeight, &Button, &ButtonBar);
if(s_ScrollRegion.AddRect(Button))
2008-01-17 23:09:49 +00:00
{
if(pEditor->UI()->MouseInside(&Button))
ShowImage = i;
static int s_NoneButton = 0;
if(pEditor->DoButton_MenuItem(i == -1 ? (void *)&s_NoneButton : &pEditor->m_Map.m_vpImages[i], i == -1 ? "None" : pEditor->m_Map.m_vpImages[i]->m_aName, i == g_SelectImageCurrent, &Button))
g_SelectImageSelected = i;
2008-01-17 23:09:49 +00:00
}
}
s_ScrollRegion.End();
if(ShowImage >= 0 && (size_t)ShowImage < pEditor->m_Map.m_vpImages.size())
{
if(ImageView.h < ImageView.w)
ImageView.w = ImageView.h;
else
ImageView.h = ImageView.w;
float Max = (float)(maximum(pEditor->m_Map.m_vpImages[ShowImage]->m_Width, pEditor->m_Map.m_vpImages[ShowImage]->m_Height));
ImageView.w *= pEditor->m_Map.m_vpImages[ShowImage]->m_Width / Max;
ImageView.h *= pEditor->m_Map.m_vpImages[ShowImage]->m_Height / Max;
pEditor->Graphics()->TextureSet(pEditor->m_Map.m_vpImages[ShowImage]->m_Texture);
pEditor->Graphics()->BlendNormal();
pEditor->Graphics()->WrapClamp();
pEditor->Graphics()->QuadsBegin();
IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h);
pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1);
pEditor->Graphics()->QuadsEnd();
pEditor->Graphics()->WrapNormal();
}
2008-01-17 23:09:49 +00:00
return CUI::POPUP_KEEP_OPEN;
2008-01-17 23:09:49 +00:00
}
2010-05-29 07:25:38 +00:00
void CEditor::PopupSelectImageInvoke(int Current, float x, float y)
2008-01-17 23:09:49 +00:00
{
static SPopupMenuId s_PopupSelectImageId;
2010-05-29 07:25:38 +00:00
g_SelectImageSelected = -100;
g_SelectImageCurrent = Current;
UI()->DoPopupMenu(&s_PopupSelectImageId, x, y, 450, 300, this, PopupSelectImage);
2008-01-17 23:09:49 +00:00
}
2010-05-29 07:25:38 +00:00
int CEditor::PopupSelectImageResult()
2008-01-17 23:09:49 +00:00
{
2010-05-29 07:25:38 +00:00
if(g_SelectImageSelected == -100)
2008-01-17 23:09:49 +00:00
return -100;
2010-05-29 07:25:38 +00:00
g_SelectImageCurrent = g_SelectImageSelected;
g_SelectImageSelected = -100;
return g_SelectImageCurrent;
2008-01-17 23:09:49 +00:00
}
2014-10-08 15:33:06 +00:00
static int g_SelectSoundSelected = -100;
static int g_SelectSoundCurrent = -100;
CUI::EPopupMenuFunctionResult CEditor::PopupSelectSound(void *pContext, CUIRect View, bool Active)
2014-10-08 15:33:06 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
const float ButtonHeight = 12.0f;
const float ButtonMargin = 2.0f;
2014-10-08 15:33:06 +00:00
static CScrollRegion s_ScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = 10.0f;
ScrollParams.m_ScrollbarMargin = 3.0f;
ScrollParams.m_ScrollUnit = (ButtonHeight + ButtonMargin) * 5;
s_ScrollRegion.Begin(&View, &ScrollOffset, &ScrollParams);
View.y += ScrollOffset.y;
2014-10-08 15:33:06 +00:00
for(int i = -1; i < (int)pEditor->m_Map.m_vpSounds.size(); i++)
2014-10-08 15:33:06 +00:00
{
CUIRect Button;
View.HSplitTop(ButtonMargin, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
if(s_ScrollRegion.AddRect(Button))
2014-10-08 15:33:06 +00:00
{
static int s_NoneButton = 0;
if(pEditor->DoButton_MenuItem(i == -1 ? (void *)&s_NoneButton : &pEditor->m_Map.m_vpSounds[i], i == -1 ? "None" : pEditor->m_Map.m_vpSounds[i]->m_aName, i == g_SelectSoundCurrent, &Button))
2014-10-08 15:33:06 +00:00
g_SelectSoundSelected = i;
}
}
s_ScrollRegion.End();
return CUI::POPUP_KEEP_OPEN;
2014-10-08 15:33:06 +00:00
}
void CEditor::PopupSelectSoundInvoke(int Current, float x, float y)
{
static SPopupMenuId s_PopupSelectSoundId;
2014-10-08 15:33:06 +00:00
g_SelectSoundSelected = -100;
g_SelectSoundCurrent = Current;
UI()->DoPopupMenu(&s_PopupSelectSoundId, x, y, 150, 300, this, PopupSelectSound);
2014-10-08 15:33:06 +00:00
}
int CEditor::PopupSelectSoundResult()
{
if(g_SelectSoundSelected == -100)
return -100;
g_SelectSoundCurrent = g_SelectSoundSelected;
g_SelectSoundSelected = -100;
return g_SelectSoundCurrent;
2015-07-09 00:08:14 +00:00
}
2014-10-08 15:33:06 +00:00
static int s_GametileOpSelected = -1;
2008-01-17 23:09:49 +00:00
static const char *s_apGametileOpButtonNames[] = {
"Air",
"Hookable",
"Death",
"Unhookable",
"Hookthrough",
"Freeze",
"Unfreeze",
"Deep Freeze",
"Deep Unfreeze",
"Blue Check-Tele",
"Red Check-Tele",
"Live Freeze",
"Live Unfreeze",
};
CUI::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CUIRect Button;
for(size_t i = 0; i < std::size(s_apGametileOpButtonNames); ++i)
{
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Button, &View);
if(pEditor->DoButton_Editor(&s_apGametileOpButtonNames[i], s_apGametileOpButtonNames[i], 0, &Button, 0, nullptr))
s_GametileOpSelected = i;
}
2008-01-17 23:09:49 +00:00
return CUI::POPUP_KEEP_OPEN;
}
2008-01-17 23:09:49 +00:00
void CEditor::PopupSelectGametileOpInvoke(float x, float y)
{
static SPopupMenuId s_PopupSelectGametileOpId;
s_GametileOpSelected = -1;
UI()->DoPopupMenu(&s_PopupSelectGametileOpId, x, y, 120.0f, std::size(s_apGametileOpButtonNames) * 14.0f + 10.0f, this, PopupSelectGametileOp);
}
2008-01-17 23:09:49 +00:00
int CEditor::PopupSelectGameTileOpResult()
{
if(s_GametileOpSelected < 0)
return -1;
int Result = s_GametileOpSelected;
s_GametileOpSelected = -1;
return Result;
}
2008-01-17 23:09:49 +00:00
static int s_AutoMapConfigSelected = -100;
static int s_AutoMapConfigCurrent = -100;
CUI::EPopupMenuFunctionResult CEditor::PopupSelectConfigAutoMap(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2020-09-20 12:05:39 +00:00
CLayerTiles *pLayer = static_cast<CLayerTiles *>(pEditor->GetSelectedLayer(0));
CAutoMapper *pAutoMapper = &pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper;
2011-08-11 08:59:14 +00:00
const float ButtonHeight = 12.0f;
2020-09-27 15:20:41 +00:00
const float ButtonMargin = 2.0f;
static CScrollRegion s_ScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = 10.0f;
ScrollParams.m_ScrollbarMargin = 3.0f;
ScrollParams.m_ScrollUnit = (ButtonHeight + ButtonMargin) * 5;
s_ScrollRegion.Begin(&View, &ScrollOffset, &ScrollParams);
View.y += ScrollOffset.y;
for(int i = -1; i < pAutoMapper->ConfigNamesNum(); i++)
{
CUIRect Button;
View.HSplitTop(ButtonMargin, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
if(s_ScrollRegion.AddRect(Button))
{
static int s_NoneButton = 0;
if(pEditor->DoButton_MenuItem(i == -1 ? (void *)&s_NoneButton : pAutoMapper->GetConfigName(i), i == -1 ? "None" : pAutoMapper->GetConfigName(i), i == s_AutoMapConfigCurrent, &Button))
s_AutoMapConfigSelected = i;
}
}
s_ScrollRegion.End();
return CUI::POPUP_KEEP_OPEN;
}
void CEditor::PopupSelectConfigAutoMapInvoke(int Current, float x, float y)
{
static SPopupMenuId s_PopupSelectConfigAutoMapId;
s_AutoMapConfigSelected = -100;
s_AutoMapConfigCurrent = Current;
2020-09-20 12:05:39 +00:00
CLayerTiles *pLayer = static_cast<CLayerTiles *>(GetSelectedLayer(0));
const int ItemCount = minimum(m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), 10);
// Width for buttons is 120, 15 is the scrollbar width, 2 is the margin between both.
UI()->DoPopupMenu(&s_PopupSelectConfigAutoMapId, x, y, 120.0f + 15.0f + 2.0f, 26.0f + 14.0f * ItemCount, this, PopupSelectConfigAutoMap);
}
int CEditor::PopupSelectConfigAutoMapResult()
{
if(s_AutoMapConfigSelected == -100)
return -100;
2011-08-11 08:59:14 +00:00
s_AutoMapConfigCurrent = s_AutoMapConfigSelected;
s_AutoMapConfigSelected = -100;
return s_AutoMapConfigCurrent;
}
// DDRace
CUI::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2020-05-20 18:57:19 +00:00
static int s_PreviousNumber = -1;
2020-05-15 22:42:11 +00:00
2020-05-17 00:56:35 +00:00
CUIRect NumberPicker;
2020-05-15 22:42:11 +00:00
CUIRect FindEmptySlot;
2020-05-17 00:56:35 +00:00
View.VSplitRight(15.f, &NumberPicker, &FindEmptySlot);
NumberPicker.VSplitRight(2.f, &NumberPicker, nullptr);
FindEmptySlot.HSplitTop(13.0f, &FindEmptySlot, nullptr);
FindEmptySlot.HMargin(1.0f, &FindEmptySlot);
2020-05-15 22:42:11 +00:00
2020-05-17 00:56:35 +00:00
// find empty number button
{
2020-05-19 16:17:02 +00:00
static int s_EmptySlotPid = 0;
if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F)))
2020-05-15 22:42:11 +00:00
{
2020-05-17 00:56:35 +00:00
int number = -1;
for(int i = 1; i <= 255; i++)
2020-05-15 22:42:11 +00:00
{
2020-05-17 00:56:35 +00:00
if(!pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(i))
{
number = i;
break;
}
2020-05-15 22:42:11 +00:00
}
2020-05-17 00:56:35 +00:00
if(number != -1)
{
pEditor->m_TeleNumber = number;
}
2020-05-15 22:42:11 +00:00
}
}
2020-05-17 00:56:35 +00:00
// number picker
{
2020-05-19 16:17:02 +00:00
static ColorRGBA s_Color = ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2008-01-17 23:09:49 +00:00
2020-05-17 00:56:35 +00:00
enum
{
PROP_TELE = 0,
NUM_PROPS,
};
CProperty aProps[] = {
{"Number", pEditor->m_TeleNumber, PROPTYPE_INT_STEP, 1, 255},
{nullptr},
2020-05-17 00:56:35 +00:00
};
2020-05-17 00:56:35 +00:00
static int s_aIds[NUM_PROPS] = {0};
static int NewVal = 0;
2020-05-19 16:17:02 +00:00
int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_Color);
2020-05-17 00:56:35 +00:00
if(Prop == PROP_TELE)
{
pEditor->m_TeleNumber = (NewVal - 1 + 255) % 255 + 1;
2020-05-17 00:56:35 +00:00
}
2020-05-20 18:57:19 +00:00
if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_TeleNumber)
2020-05-17 00:56:35 +00:00
{
s_Color = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleNumber) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2020-05-17 00:56:35 +00:00
}
2020-05-15 22:42:11 +00:00
}
2020-05-20 18:57:19 +00:00
s_PreviousNumber = pEditor->m_TeleNumber;
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupSpeedup(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
enum
{
PROP_FORCE = 0,
PROP_MAXSPEED,
PROP_ANGLE,
NUM_PROPS
};
CProperty aProps[] = {
{"Force", pEditor->m_SpeedupForce, PROPTYPE_INT_STEP, 1, 255},
2016-06-02 13:31:12 +00:00
{"Max Speed", pEditor->m_SpeedupMaxSpeed, PROPTYPE_INT_STEP, 0, 255},
{"Angle", pEditor->m_SpeedupAngle, PROPTYPE_ANGLE_SCROLL, 0, 359},
{nullptr},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop == PROP_FORCE)
2023-04-01 21:21:13 +00:00
{
pEditor->m_SpeedupForce = clamp(NewVal, 1, 255);
2023-04-01 21:21:13 +00:00
}
else if(Prop == PROP_MAXSPEED)
{
pEditor->m_SpeedupMaxSpeed = clamp(NewVal, 0, 255);
2023-04-01 21:21:13 +00:00
}
else if(Prop == PROP_ANGLE)
{
pEditor->m_SpeedupAngle = clamp(NewVal, 0, 359);
2023-04-01 21:21:13 +00:00
}
return CUI::POPUP_KEEP_OPEN;
}
2008-01-17 23:09:49 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2023-04-01 21:26:52 +00:00
CUIRect NumberPicker, FindEmptySlot;
2008-01-17 23:09:49 +00:00
2023-04-01 21:26:52 +00:00
View.VSplitRight(15.0f, &NumberPicker, &FindEmptySlot);
NumberPicker.VSplitRight(2.0f, &NumberPicker, nullptr);
FindEmptySlot.HSplitTop(13.0f, &FindEmptySlot, nullptr);
FindEmptySlot.HMargin(1.0f, &FindEmptySlot);
2020-05-17 00:56:35 +00:00
// find empty number button
{
2020-05-19 16:17:02 +00:00
static int s_EmptySlotPid = 0;
if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F)))
{
2023-04-01 21:26:52 +00:00
int Number = -1;
2020-05-17 00:56:35 +00:00
for(int i = 1; i <= 255; i++)
{
2020-05-17 00:56:35 +00:00
if(!pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(i))
{
2023-04-01 21:26:52 +00:00
Number = i;
2020-05-17 00:56:35 +00:00
break;
}
}
2020-05-17 00:56:35 +00:00
2023-04-01 21:26:52 +00:00
if(Number != -1)
2020-05-17 00:56:35 +00:00
{
2023-04-01 21:26:52 +00:00
pEditor->m_SwitchNum = Number;
2020-05-17 00:56:35 +00:00
}
}
2020-05-17 00:56:35 +00:00
}
2020-05-17 00:56:35 +00:00
// number picker
2023-04-01 21:26:52 +00:00
static int s_PreviousNumber = -1;
2020-05-17 00:56:35 +00:00
{
2020-05-19 16:17:02 +00:00
static ColorRGBA s_Color = ColorRGBA(1, 1, 1, 0.5f);
2020-05-17 00:56:35 +00:00
enum
{
2023-04-01 21:26:52 +00:00
PROP_SWITCH_NUMBER = 0,
PROP_SWITCH_DELAY,
2020-05-17 00:56:35 +00:00
NUM_PROPS,
};
CProperty aProps[] = {
{"Number", pEditor->m_SwitchNum, PROPTYPE_INT_STEP, 0, 255},
2020-05-17 00:56:35 +00:00
{"Delay", pEditor->m_SwitchDelay, PROPTYPE_INT_STEP, 0, 255},
{nullptr},
};
2020-05-17 00:56:35 +00:00
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
2020-05-19 16:17:02 +00:00
int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_Color);
2020-05-17 00:56:35 +00:00
2023-04-01 21:26:52 +00:00
if(Prop == PROP_SWITCH_NUMBER)
2020-05-17 00:56:35 +00:00
{
pEditor->m_SwitchNum = (NewVal + 256) % 256;
2020-05-17 00:56:35 +00:00
}
2023-04-01 21:26:52 +00:00
else if(Prop == PROP_SWITCH_DELAY)
2020-05-17 00:56:35 +00:00
{
pEditor->m_SwitchDelay = (NewVal + 256) % 256;
}
2020-05-20 18:57:19 +00:00
if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_SwitchNum)
2020-05-17 00:56:35 +00:00
{
s_Color = pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(pEditor->m_SwitchNum) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2020-05-17 00:56:35 +00:00
}
}
2020-05-20 18:57:19 +00:00
s_PreviousNumber = pEditor->m_SwitchNum;
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupTune(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
enum
{
PROP_TUNE = 0,
NUM_PROPS,
};
CProperty aProps[] = {
{"Zone", pEditor->m_TuningNum, PROPTYPE_INT_STEP, 1, 255},
{nullptr},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop == PROP_TUNE)
2023-04-01 21:22:03 +00:00
{
pEditor->m_TuningNum = (NewVal - 1 + 255) % 255 + 1;
2023-04-01 21:22:03 +00:00
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::EPopupMenuFunctionResult CEditor::PopupGoto(void *pContext, CUIRect View, bool Active)
2022-08-20 18:47:46 +00:00
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2022-08-20 18:47:46 +00:00
enum
{
2023-04-01 10:35:29 +00:00
PROP_COORD_X = 0,
PROP_COORD_Y,
2022-08-20 18:47:46 +00:00
NUM_PROPS,
};
2023-04-01 10:35:29 +00:00
static ivec2 s_GotoPos(0, 0);
2022-08-20 18:47:46 +00:00
CProperty aProps[] = {
2023-04-01 10:35:29 +00:00
{"X", s_GotoPos.x, PROPTYPE_INT_STEP, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()},
{"Y", s_GotoPos.y, PROPTYPE_INT_STEP, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()},
2022-08-20 18:47:46 +00:00
{nullptr},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
2023-04-01 10:35:29 +00:00
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal, ColorRGBA(1, 1, 1, 0.5f));
2022-08-20 18:47:46 +00:00
2023-04-01 10:35:29 +00:00
if(Prop == PROP_COORD_X)
2022-08-20 18:47:46 +00:00
{
2023-04-01 10:35:29 +00:00
s_GotoPos.x = NewVal;
2022-08-20 18:47:46 +00:00
}
2023-04-01 10:35:29 +00:00
else if(Prop == PROP_COORD_Y)
2022-08-20 18:47:46 +00:00
{
2023-04-01 10:35:29 +00:00
s_GotoPos.y = NewVal;
2022-08-20 18:47:46 +00:00
}
CUIRect Button;
View.HSplitBottom(12.0f, &View, &Button);
static int s_Button;
2023-04-01 10:35:29 +00:00
if(pEditor->DoButton_Editor(&s_Button, "Go", 0, &Button, 0, nullptr))
2022-08-20 18:47:46 +00:00
{
2023-04-01 10:35:29 +00:00
pEditor->Goto(s_GotoPos.x + 0.5f, s_GotoPos.y + 0.5f);
2022-08-20 18:47:46 +00:00
}
return CUI::POPUP_KEEP_OPEN;
2022-08-20 18:47:46 +00:00
}
CUI::EPopupMenuFunctionResult CEditor::PopupEntities(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
2023-04-01 21:22:27 +00:00
for(size_t i = 0; i < pEditor->m_vSelectEntitiesFiles.size(); i++)
{
CUIRect Button;
View.HSplitTop(14.0f, &Button, &View);
const char *pName = pEditor->m_vSelectEntitiesFiles[i].c_str();
if(pEditor->DoButton_MenuItem(pName, pName, pEditor->m_vSelectEntitiesFiles[i] == pEditor->m_SelectEntitiesImage, &Button))
{
if(pEditor->m_vSelectEntitiesFiles[i] != pEditor->m_SelectEntitiesImage)
{
pEditor->m_SelectEntitiesImage = pEditor->m_vSelectEntitiesFiles[i];
pEditor->m_AllowPlaceUnusedTiles = pEditor->m_SelectEntitiesImage == "DDNet" ? 0 : -1;
pEditor->m_PreventUnusedTilesWasWarned = false;
if(pEditor->m_EntitiesTexture.IsValid())
pEditor->Graphics()->UnloadTexture(&pEditor->m_EntitiesTexture);
2023-04-01 21:22:27 +00:00
char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "editor/entities/%s.png", pName);
pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(aBuf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, pEditor->GetTextureUsageFlag());
return CUI::POPUP_CLOSE_CURRENT;
}
}
}
return CUI::POPUP_KEEP_OPEN;
}
2023-05-01 21:07:58 +00:00
CUI::EPopupMenuFunctionResult CEditor::PopupProofMode(void *pContext, CUIRect View, bool Active)
{
CEditor *pEditor = static_cast<CEditor *>(pContext);
CUIRect Button;
View.HSplitTop(12.0f, &Button, &View);
static int s_ButtonIngame;
2023-05-21 19:26:23 +00:00
if(pEditor->DoButton_MenuItem(&s_ButtonIngame, "Ingame", pEditor->m_ProofBorders == PROOF_BORDER_INGAME, &Button, 0, "These borders represent what a player maximum can see."))
2023-05-01 21:07:58 +00:00
{
2023-05-21 16:00:41 +00:00
pEditor->m_ProofBorders = PROOF_BORDER_INGAME;
2023-05-01 21:07:58 +00:00
return CUI::POPUP_CLOSE_CURRENT;
}
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Button, &View);
static int s_ButtonMenu;
2023-05-21 19:26:23 +00:00
if(pEditor->DoButton_MenuItem(&s_ButtonMenu, "Menu", pEditor->m_ProofBorders == PROOF_BORDER_MENU, &Button, 0, "These borders represent what will be shown in the menu."))
2023-05-01 21:07:58 +00:00
{
2023-05-21 19:26:23 +00:00
pEditor->m_ProofBorders = PROOF_BORDER_MENU;
2023-05-01 21:07:58 +00:00
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}