#include "GameView.h" #include "Brush.h" #include "DecorationTool.h" #include "Favorite.h" #include "Format.h" #include "GameController.h" #include "GameModel.h" #include "IntroText.h" #include "Menu.h" #include "MenuButton.h" #include "Misc.h" #include "Notification.h" #include "ToolButton.h" #include "QuickOptions.h" #include "client/SaveInfo.h" #include "client/SaveFile.h" #include "client/Client.h" #include "client/GameSave.h" #include "common/platform/Platform.h" #include "graphics/Graphics.h" #include "graphics/Renderer.h" #include "gui/Style.h" #include "simulation/ElementClasses.h" #include "simulation/ElementDefs.h" #include "simulation/SaveRenderer.h" #include "simulation/SimulationData.h" #include "gui/dialogues/ConfirmPrompt.h" #include "gui/dialogues/ErrorMessage.h" #include "gui/dialogues/InformationMessage.h" #include "gui/interface/Button.h" #include "gui/interface/Colour.h" #include "gui/interface/Engine.h" #include "Config.h" #include #include #include #include class SplitButton : public ui::Button { bool rightDown; bool leftDown; bool showSplit; int splitPosition; String toolTip2; struct SplitButtonAction { std::function left, right; }; SplitButtonAction actionCallback; public: SplitButton(ui::Point position, ui::Point size, String buttonText, String toolTip, String toolTip2, int split) : Button(position, size, buttonText, toolTip), showSplit(true), splitPosition(split), toolTip2(toolTip2) { } virtual ~SplitButton() = default; void SetRightToolTip(String tooltip) { toolTip2 = tooltip; } bool GetShowSplit() { return showSplit; } void SetShowSplit(bool split) { showSplit = split; } inline SplitButtonAction const &GetSplitActionCallback() { return actionCallback; } inline void SetSplitActionCallback(SplitButtonAction const &action) { actionCallback = action; } void SetToolTip(int x, int y) { if(x >= splitPosition || !showSplit) { if(toolTip2.length()>0 && GetParentWindow()) { GetParentWindow()->ToolTip(Position, toolTip2); } } else if(x < splitPosition) { if(toolTip.length()>0 && GetParentWindow()) { GetParentWindow()->ToolTip(Position, toolTip); } } } void OnMouseClick(int x, int y, unsigned int button) override { if(isButtonDown) { if(leftDown) DoLeftAction(); else if(rightDown) DoRightAction(); } ui::Button::OnMouseClick(x, y, button); } void OnMouseHover(int x, int y) override { SetToolTip(x, y); } void OnMouseEnter(int x, int y) override { isMouseInside = true; if(!Enabled) return; SetToolTip(x, y); } void TextPosition(String ButtonText) override { ui::Button::TextPosition(ButtonText); textPosition.X += 3; } void SetToolTips(String newToolTip1, String newToolTip2) { toolTip = newToolTip1; toolTip2 = newToolTip2; } void OnMouseDown(int x, int y, unsigned int button) override { ui::Button::OnMouseDown(x, y, button); if (MouseDownInside) { rightDown = false; leftDown = false; if(x - Position.X >= splitPosition) rightDown = true; else if(x - Position.X < splitPosition) leftDown = true; } } void DoRightAction() { if(!Enabled) return; if (actionCallback.right) actionCallback.right(); } void DoLeftAction() { if(!Enabled) return; if (actionCallback.left) actionCallback.left(); } void Draw(const ui::Point& screenPos) override { ui::Button::Draw(screenPos); Graphics * g = GetGraphics(); drawn = true; if(showSplit) g->DrawLine(screenPos + Vec2{ splitPosition, 1 }, screenPos + Vec2{ splitPosition, Size.Y-2 }, 0xB4B4B4_rgb); } }; GameView::GameView(): ui::Window(ui::Point(0, 0), ui::Point(WINDOWW, WINDOWH)), isMouseDown(false), skipDraw(false), zoomEnabled(false), zoomCursorFixed(false), mouseInZoom(false), drawSnap(false), shiftBehaviour(false), ctrlBehaviour(false), altBehaviour(false), showHud(true), showBrush(true), showDebug(false), delayedActiveMenu(-1), wallBrush(false), toolBrush(false), decoBrush(false), windTool(false), toolIndex(0), currentSaveType(0), lastMenu(-1), toolTipPresence(0), toolTip(""), isToolTipFadingIn(false), toolTipPosition(-1, -1), infoTipPresence(0), infoTip(""), buttonTipShow(0), buttonTip(""), isButtonTipFadingIn(false), introText(2048), introTextMessage(IntroText().FromUtf8()), doScreenshot(false), screenshotIndex(1), lastScreenshotTime(0), recordingIndex(0), recording(false), recordingFolder(0), currentPoint(ui::Point(0, 0)), lastPoint(ui::Point(0, 0)), ren(NULL), activeBrush(NULL), saveSimulationButtonEnabled(false), saveReuploadAllowed(true), drawMode(DrawPoints), drawPoint1(0, 0), drawPoint2(0, 0), selectMode(SelectNone), selectPoint1(0, 0), selectPoint2(0, 0), currentMouse(0, 0), mousePosition(0, 0) { int currentX = 1; //Set up UI scrollBar = new ui::Button(ui::Point(0,YRES+21), ui::Point(XRES, 2), ""); scrollBar->Appearance.BorderHover = ui::Colour(200, 200, 200); scrollBar->Appearance.BorderActive = ui::Colour(200, 200, 200); scrollBar->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; scrollBar->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; AddComponent(scrollBar); searchButton = new ui::Button(ui::Point(currentX, Size.Y-16), ui::Point(17, 15), "", "Find & open a simulation. Hold Ctrl to load offline saves."); //Open searchButton->SetIcon(IconOpen); currentX+=18; searchButton->SetTogglable(false); searchButton->SetActionCallback({ [this] { if (CtrlBehaviour()) c->OpenLocalBrowse(); else c->OpenSearch(""); } }); AddComponent(searchButton); reloadButton = new ui::Button(ui::Point(currentX, Size.Y-16), ui::Point(17, 15), "", "Reload the simulation"); reloadButton->SetIcon(IconReload); reloadButton->Appearance.Margin.Left+=2; currentX+=18; reloadButton->SetActionCallback({ [this] { c->ReloadSim(); }, [this] { c->OpenSavePreview(); } }); AddComponent(reloadButton); saveSimulationButton = new SplitButton(ui::Point(currentX, Size.Y-16), ui::Point(150, 15), "[untitled simulation]", "", "", 19); saveSimulationButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; saveSimulationButton->SetIcon(IconSave); currentX+=151; saveSimulationButton->SetSplitActionCallback({ [this] { if (CtrlBehaviour() || !Client::Ref().GetAuthUser().UserID) c->OpenLocalSaveWindow(true); else c->SaveAsCurrent(); }, [this] { if (CtrlBehaviour() || !Client::Ref().GetAuthUser().UserID) c->OpenLocalSaveWindow(false); else c->OpenSaveWindow(); } }); SetSaveButtonTooltips(); AddComponent(saveSimulationButton); upVoteButton = new ui::Button(ui::Point(currentX, Size.Y-16), ui::Point(39, 15), "", "Like this save"); upVoteButton->SetIcon(IconVoteUp); upVoteButton->Appearance.Margin.Top+=2; upVoteButton->Appearance.Margin.Left+=2; currentX+=38; AddComponent(upVoteButton); downVoteButton = new ui::Button(ui::Point(currentX, Size.Y-16), ui::Point(15, 15), "", "Dislike this save"); downVoteButton->SetIcon(IconVoteDown); downVoteButton->Appearance.Margin.Bottom+=2; downVoteButton->Appearance.Margin.Left+=2; currentX+=16; AddComponent(downVoteButton); tagSimulationButton = new ui::Button(ui::Point(currentX, Size.Y-16), ui::Point(227, 15), "[no tags set]", "Add simulation tags"); tagSimulationButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; tagSimulationButton->SetIcon(IconTag); //currentX+=252; tagSimulationButton->SetActionCallback({ [this] { c->OpenTags(); } }); AddComponent(tagSimulationButton); clearSimButton = new ui::Button(ui::Point(Size.X-159, Size.Y-16), ui::Point(17, 15), "", "Erase everything"); clearSimButton->SetIcon(IconNew); clearSimButton->Appearance.Margin.Left+=2; clearSimButton->SetActionCallback({ [this] { c->ClearSim(); } }); AddComponent(clearSimButton); loginButton = new SplitButton(ui::Point(Size.X-141, Size.Y-16), ui::Point(92, 15), "[sign in]", "Sign into simulation server", "Edit Profile", 19); loginButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; loginButton->SetIcon(IconLogin); loginButton->SetSplitActionCallback({ [this] { c->OpenLogin(); }, [this] { c->OpenProfile(); } }); AddComponent(loginButton); simulationOptionButton = new ui::Button(ui::Point(Size.X-48, Size.Y-16), ui::Point(15, 15), "", "Simulation options"); simulationOptionButton->SetIcon(IconSimulationSettings); simulationOptionButton->Appearance.Margin.Left+=2; simulationOptionButton->SetActionCallback({ [this] { c->OpenOptions(); } }); AddComponent(simulationOptionButton); displayModeButton = new ui::Button(ui::Point(Size.X-32, Size.Y-16), ui::Point(15, 15), "", "Renderer options"); displayModeButton->SetIcon(IconRenderSettings); displayModeButton->Appearance.Margin.Left+=2; displayModeButton->SetActionCallback({ [this] { c->OpenRenderOptions(); } }); AddComponent(displayModeButton); pauseButton = new ui::Button(ui::Point(Size.X-16, Size.Y-16), ui::Point(15, 15), "", "Pause/Resume the simulation"); //Pause pauseButton->SetIcon(IconPause); pauseButton->SetTogglable(true); pauseButton->SetActionCallback({ [this] { c->SetPaused(pauseButton->GetToggleState()); } }); AddComponent(pauseButton); ui::Button * tempButton = new ui::Button(ui::Point(WINDOWW-16, WINDOWH-32), ui::Point(15, 15), 0xE065, "Search for elements"); tempButton->Appearance.Margin = ui::Border(0, 2, 3, 2); tempButton->SetActionCallback({ [this] { c->OpenElementSearch(); } }); AddComponent(tempButton); colourPicker = new ui::Button(ui::Point((XRES/2)-8, YRES+1), ui::Point(16, 16), "", "Pick Colour"); colourPicker->SetActionCallback({ [this] { c->OpenColourPicker(); } }); } GameView::~GameView() { if(!colourPicker->GetParentWindow()) delete colourPicker; for(std::vector::iterator iter = colourPresets.begin(), end = colourPresets.end(); iter != end; ++iter) { ToolButton * button = *iter; if(!button->GetParentWindow()) { delete button; } } } class GameView::OptionListener: public QuickOptionListener { ui::Button * button; public: OptionListener(ui::Button * _button) { button = _button; } void OnValueChanged(QuickOption * option) override { switch(option->GetType()) { case QuickOption::Toggle: button->SetTogglable(true); button->SetToggleState(option->GetToggle()); break; default: break; } } }; void GameView::NotifyQuickOptionsChanged(GameModel * sender) { for (size_t i = 0; i < quickOptionButtons.size(); i++) { RemoveComponent(quickOptionButtons[i]); delete quickOptionButtons[i]; } int currentY = 1; std::vector optionList = sender->GetQuickOptions(); for(auto *option : optionList) { ui::Button * tempButton = new ui::Button(ui::Point(WINDOWW-16, currentY), ui::Point(15, 15), option->GetIcon(), option->GetDescription()); //tempButton->Appearance.Margin = ui::Border(0, 2, 3, 2); tempButton->SetTogglable(true); tempButton->SetActionCallback({ [option] { option->Perform(); } }); option->AddListener(new OptionListener(tempButton)); AddComponent(tempButton); quickOptionButtons.push_back(tempButton); currentY += 16; } } void GameView::NotifyMenuListChanged(GameModel * sender) { int currentY = WINDOWH-48;//-(sender->GetMenuList().size()*16); for (size_t i = 0; i < menuButtons.size(); i++) { RemoveComponent(menuButtons[i]); delete menuButtons[i]; } menuButtons.clear(); for (size_t i = 0; i < toolButtons.size(); i++) { RemoveComponent(toolButtons[i]); delete toolButtons[i]; } toolButtons.clear(); std::vector menuList = sender->GetMenuList(); for (int i = (int)menuList.size()-1; i >= 0; i--) { if (menuList[i]->GetVisible()) { String tempString = ""; tempString += menuList[i]->GetIcon(); String description = menuList[i]->GetDescription(); if (i == SC_FAVORITES && !Favorite::Ref().AnyFavorites()) description += " (Use ctrl+shift+click to toggle the favorite status of an element)"; auto *tempButton = new MenuButton(ui::Point(WINDOWW-16, currentY), ui::Point(15, 15), tempString, description); tempButton->Appearance.Margin = ui::Border(0, 2, 3, 2); tempButton->menuID = i; tempButton->needsClick = i == SC_DECO; tempButton->SetTogglable(true); auto mouseEnterCallback = [this, tempButton] { // don't immediately change the active menu, the actual set is done inside GameView::OnMouseMove // if we change it here it causes components to be removed, which causes the window to stop sending events // and then the previous menusection button never gets sent the OnMouseLeave event and is never unhighlighted if(!(tempButton->needsClick || c->GetMouseClickRequired()) && !GetMouseDown()) SetActiveMenuDelayed(tempButton->menuID); }; auto actionCallback = [this, tempButton, mouseEnterCallback] { if (tempButton->needsClick || c->GetMouseClickRequired()) c->SetActiveMenu(tempButton->menuID); else mouseEnterCallback(); }; tempButton->SetActionCallback({ actionCallback, nullptr, mouseEnterCallback }); currentY-=16; AddComponent(tempButton); menuButtons.push_back(tempButton); } } } void GameView::SetSample(SimulationSample sample) { this->sample = sample; } void GameView::SetHudEnable(bool hudState) { showHud = hudState; } bool GameView::GetHudEnable() { return showHud; } void GameView::SetBrushEnable(bool brushState) { showBrush = brushState; } bool GameView::GetBrushEnable() { return showBrush; } void GameView::SetDebugHUD(bool mode) { showDebug = mode; if (ren) ren->debugLines = showDebug; } bool GameView::GetDebugHUD() { return showDebug; } ui::Point GameView::GetMousePosition() { return currentMouse; } bool GameView::GetPlacingSave() { return selectMode != SelectNone; } bool GameView::GetPlacingZoom() { return zoomEnabled && !zoomCursorFixed; } void GameView::NotifyActiveToolsChanged(GameModel * sender) { for (size_t i = 0; i < toolButtons.size(); i++) { auto *tool = toolButtons[i]->tool; // Primary if (sender->GetActiveTool(0) == tool) toolButtons[i]->SetSelectionState(0); // Secondary else if (sender->GetActiveTool(1) == tool) toolButtons[i]->SetSelectionState(1); // Tertiary else if (sender->GetActiveTool(2) == tool) toolButtons[i]->SetSelectionState(2); // Replace Mode else if (sender->GetActiveTool(3) == tool) toolButtons[i]->SetSelectionState(3); // Not selected at all else toolButtons[i]->SetSelectionState(-1); } decoBrush = sender->GetActiveTool(0)->Identifier.BeginsWith("DEFAULT_DECOR_"); if (sender->GetRenderer()->findingElement) { ren->findingElement = FindingElementCandidate(); } } void GameView::NotifyLastToolChanged(GameModel * sender) { if (sender->GetLastTool()) { wallBrush = sender->GetLastTool()->Blocky; toolBrush = sender->GetLastTool()->Identifier.BeginsWith("DEFAULT_TOOL_"); } } void GameView::NotifyToolListChanged(GameModel * sender) { for (size_t i = 0; i < menuButtons.size(); i++) { if (menuButtons[i]->menuID==sender->GetActiveMenu()) { menuButtons[i]->SetToggleState(true); } else { menuButtons[i]->SetToggleState(false); } } for (size_t i = 0; i < toolButtons.size(); i++) { RemoveComponent(toolButtons[i]); delete toolButtons[i]; } toolButtons.clear(); std::vector toolList = sender->GetToolList(); int currentX = 0; for (size_t i = 0; i < toolList.size(); i++) { auto *tool = toolList[i]; auto tempTexture = tool->GetTexture(Vec2(26, 14)); ToolButton * tempButton; //get decotool texture manually, since it changes depending on it's own color if (sender->GetActiveMenu() == SC_DECO) tempTexture = ((DecorationTool*)tool)->GetIcon(tool->ToolID, Vec2(26, 14)); if (tempTexture) tempButton = new ToolButton(ui::Point(currentX, YRES+1), ui::Point(30, 18), "", tool->Identifier, tool->Description); else tempButton = new ToolButton(ui::Point(currentX, YRES+1), ui::Point(30, 18), tool->Name, tool->Identifier, tool->Description); tempButton->ClipRect = RectSized(Vec2(1, RES.Y + 1), Vec2(RES.X - 1, 18)); //currentY -= 17; currentX -= 31; tempButton->tool = tool; tempButton->SetActionCallback({ [this, tempButton] { auto *tool = tempButton->tool; if (ShiftBehaviour() && CtrlBehaviour() && !AltBehaviour()) { if (tempButton->GetSelectionState() == 0) { if (Favorite::Ref().IsFavorite(tool->Identifier)) { Favorite::Ref().RemoveFavorite(tool->Identifier); } else { Favorite::Ref().AddFavorite(tool->Identifier); } c->RebuildFavoritesMenu(); } else if (tempButton->GetSelectionState() == 1) { auto identifier = tool->Identifier; if (Favorite::Ref().IsFavorite(identifier)) { Favorite::Ref().RemoveFavorite(identifier); c->RebuildFavoritesMenu(); } else if (identifier.BeginsWith("DEFAULT_PT_LIFECUST_")) { new ConfirmPrompt("Remove custom GOL type", "Are you sure you want to remove " + identifier.Substr(20).FromUtf8() + "?", { [this, identifier]() { c->RemoveCustomGOLType(identifier); } }); } } } else { if (CtrlBehaviour() && AltBehaviour() && !ShiftBehaviour()) { if (tool->Identifier.Contains("_PT_")) { tempButton->SetSelectionState(3); } } if (tempButton->GetSelectionState() >= 0 && tempButton->GetSelectionState() <= 3) c->SetActiveTool(tempButton->GetSelectionState(), tool); } } }); tempButton->Appearance.SetTexture(std::move(tempTexture)); tempButton->Appearance.BackgroundInactive = toolList[i]->Colour.WithAlpha(0xFF); if(sender->GetActiveTool(0) == toolList[i]) { tempButton->SetSelectionState(0); //Primary } else if(sender->GetActiveTool(1) == toolList[i]) { tempButton->SetSelectionState(1); //Secondary } else if(sender->GetActiveTool(2) == toolList[i]) { tempButton->SetSelectionState(2); //Tertiary } else if(sender->GetActiveTool(3) == toolList[i]) { tempButton->SetSelectionState(3); //Replace mode } tempButton->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; tempButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; AddComponent(tempButton); toolButtons.push_back(tempButton); } if (sender->GetActiveMenu() != SC_DECO) lastMenu = sender->GetActiveMenu(); updateToolButtonScroll(); } void GameView::NotifyColourSelectorVisibilityChanged(GameModel * sender) { for(std::vector::iterator iter = colourPresets.begin(), end = colourPresets.end(); iter != end; ++iter) { ToolButton * button = *iter; RemoveComponent(button); button->SetParentWindow(NULL); } RemoveComponent(colourPicker); colourPicker->SetParentWindow(NULL); if(sender->GetColourSelectorVisibility()) { for(std::vector::iterator iter = colourPresets.begin(), end = colourPresets.end(); iter != end; ++iter) { ToolButton * button = *iter; AddComponent(button); } AddComponent(colourPicker); c->SetActiveColourPreset(-1); } } void GameView::NotifyColourPresetsChanged(GameModel * sender) { for (auto *button : colourPresets) { RemoveComponent(button); delete button; } colourPresets.clear(); int currentX = 5; std::vector colours = sender->GetColourPresets(); int i = 0; for(std::vector::iterator iter = colours.begin(), end = colours.end(); iter != end; ++iter) { ToolButton * tempButton = new ToolButton(ui::Point(currentX, YRES+1), ui::Point(30, 18), "", "", "Decoration Presets."); tempButton->Appearance.BackgroundInactive = *iter; tempButton->SetActionCallback({ [this, i, tempButton] { c->SetActiveColourPreset(i); c->SetColour(tempButton->Appearance.BackgroundInactive); } }); currentX += 31; if(sender->GetColourSelectorVisibility()) AddComponent(tempButton); colourPresets.push_back(tempButton); i++; } NotifyColourActivePresetChanged(sender); } void GameView::NotifyColourActivePresetChanged(GameModel * sender) { for (size_t i = 0; i < colourPresets.size(); i++) { if (sender->GetActiveColourPreset() == i) { colourPresets[i]->SetSelectionState(0); //Primary } else { colourPresets[i]->SetSelectionState(-1); } } } void GameView::NotifyColourSelectorColourChanged(GameModel * sender) { colourPicker->Appearance.BackgroundInactive = sender->GetColourSelectorColour(); colourPicker->Appearance.BackgroundHover = sender->GetColourSelectorColour(); NotifyToolListChanged(sender); } void GameView::NotifyRendererChanged(GameModel * sender) { ren = sender->GetRenderer(); } void GameView::NotifySimulationChanged(GameModel * sender) { } void GameView::NotifyUserChanged(GameModel * sender) { if(!sender->GetUser().UserID) { loginButton->SetText("[sign in]"); loginButton->SetShowSplit(false); loginButton->SetRightToolTip("Sign in to simulation server"); } else { loginButton->SetText(sender->GetUser().Username.FromUtf8()); loginButton->SetShowSplit(true); loginButton->SetRightToolTip("Edit profile"); } // saveSimulationButtonEnabled = sender->GetUser().ID; saveSimulationButtonEnabled = true; NotifySaveChanged(sender); } void GameView::NotifyPausedChanged(GameModel * sender) { pauseButton->SetToggleState(sender->GetPaused()); } void GameView::NotifyToolTipChanged(GameModel * sender) { toolTip = sender->GetToolTip(); } void GameView::NotifyInfoTipChanged(GameModel * sender) { infoTip = sender->GetInfoTip(); infoTipPresence = 120; } void GameView::NotifySaveChanged(GameModel * sender) { saveReuploadAllowed = true; if (sender->GetSave()) { if (introText > 50) introText = 50; saveSimulationButton->SetText(sender->GetSave()->GetName()); if (sender->GetSave()->GetUserName() == sender->GetUser().Username) saveSimulationButton->SetShowSplit(true); else saveSimulationButton->SetShowSplit(false); reloadButton->Enabled = true; upVoteButton->Enabled = sender->GetSave()->GetID() && sender->GetUser().UserID && sender->GetUser().Username != sender->GetSave()->GetUserName(); if(sender->GetSave()->GetID() && sender->GetUser().UserID && sender->GetSave()->GetVote()==1) { upVoteButton->Appearance.BackgroundHover = (ui::Colour(20, 128, 30, 255)); upVoteButton->Appearance.BackgroundInactive = (ui::Colour(0, 108, 10, 255)); upVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 108, 10, 255)); } else { upVoteButton->Appearance.BackgroundHover = (ui::Colour(20, 20, 20)); upVoteButton->Appearance.BackgroundInactive = (ui::Colour(0, 0, 0)); upVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); } downVoteButton->Enabled = upVoteButton->Enabled; if (sender->GetSave()->GetID() && sender->GetUser().UserID && sender->GetSave()->GetVote()==-1) { downVoteButton->Appearance.BackgroundHover = (ui::Colour(128, 20, 30, 255)); downVoteButton->Appearance.BackgroundInactive = (ui::Colour(108, 0, 10, 255)); downVoteButton->Appearance.BackgroundDisabled = (ui::Colour(108, 0, 10, 255)); } else { downVoteButton->Appearance.BackgroundHover = (ui::Colour(20, 20, 20)); downVoteButton->Appearance.BackgroundInactive = (ui::Colour(0, 0, 0)); downVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); } if (sender->GetUser().UserID) { upVoteButton->Appearance.BorderDisabled = upVoteButton->Appearance.BorderInactive; downVoteButton->Appearance.BorderDisabled = downVoteButton->Appearance.BorderInactive; } else { upVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100); downVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100); } if (sender->GetSave()->GetVote() == 1) upVoteButton->SetActionCallback({ [this] { c->Vote(0); } }); else upVoteButton->SetActionCallback({ [this] { c->Vote(1); } }); if (sender->GetSave()->GetVote() == -1) downVoteButton->SetActionCallback({ [this] { c->Vote(0) ;} }); else downVoteButton->SetActionCallback({ [this] { c->Vote(-1) ;} }); tagSimulationButton->Enabled = sender->GetSave()->GetID(); if (sender->GetSave()->GetID()) { StringBuilder tagsStream; std::list tags = sender->GetSave()->GetTags(); if (tags.size()) { for (std::list::const_iterator iter = tags.begin(), begin = tags.begin(), end = tags.end(); iter != end; iter++) { if (iter != begin) tagsStream << " "; tagsStream << iter->FromUtf8(); } tagSimulationButton->SetText(tagsStream.Build()); } else { tagSimulationButton->SetText("[no tags set]"); } } else { tagSimulationButton->SetText("[no tags set]"); } currentSaveType = 1; int saveID = sender->GetSave()->GetID(); if (saveID == 404 || saveID == 2157797) saveReuploadAllowed = false; } else if (sender->GetSaveFile()) { if (ctrlBehaviour) saveSimulationButton->SetShowSplit(true); else saveSimulationButton->SetShowSplit(false); saveSimulationButton->SetText(sender->GetSaveFile()->GetDisplayName()); reloadButton->Enabled = true; upVoteButton->Enabled = false; upVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); upVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100); downVoteButton->Enabled = false; downVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); downVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100); tagSimulationButton->Enabled = false; tagSimulationButton->SetText("[no tags set]"); currentSaveType = 2; } else { saveSimulationButton->SetShowSplit(false); saveSimulationButton->SetText("[untitled simulation]"); reloadButton->Enabled = false; upVoteButton->Enabled = false; upVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); upVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100), downVoteButton->Enabled = false; downVoteButton->Appearance.BackgroundDisabled = (ui::Colour(0, 0, 0)); downVoteButton->Appearance.BorderDisabled = ui::Colour(100, 100, 100), tagSimulationButton->Enabled = false; tagSimulationButton->SetText("[no tags set]"); currentSaveType = 0; } saveSimulationButton->Enabled = (saveSimulationButtonEnabled && saveReuploadAllowed) || ctrlBehaviour; SetSaveButtonTooltips(); } void GameView::NotifyBrushChanged(GameModel * sender) { activeBrush = &sender->GetBrush(); } ByteString GameView::TakeScreenshot(int captureUI, int fileType) { std::unique_ptr screenshot; if (captureUI) { screenshot = std::make_unique(ren->DumpFrame()); } else { screenshot = std::make_unique(ui::Engine::Ref().g->DumpFrame()); } ByteString filename; { // Optional suffix to distinguish screenshots taken at the exact same time ByteString suffix = ""; time_t screenshotTime = time(nullptr); if (screenshotTime == lastScreenshotTime) { screenshotIndex++; suffix = ByteString::Build(" (", screenshotIndex, ")"); } else { screenshotIndex = 1; } lastScreenshotTime = screenshotTime; std::string date = format::UnixtimeToDate(screenshotTime, "%Y-%m-%d %H.%M.%S"); filename = ByteString::Build("screenshot ", date, suffix); } if (fileType == 1) { filename += ".bmp"; // We should be able to simply use SDL_PIXELFORMAT_XRGB8888 here with a bit depth of 32 to convert RGBA data to RGB data, // and save the resulting surface directly. However, ubuntu-18.04 ships SDL2 so old that it doesn't have // SDL_PIXELFORMAT_XRGB8888, so we first create an RGBA surface and then convert it. auto *rgbaSurface = SDL_CreateRGBSurfaceWithFormatFrom(screenshot->Data(), screenshot->Size().X, screenshot->Size().Y, 32, screenshot->Size().X * sizeof(pixel), SDL_PIXELFORMAT_ARGB8888); auto *rgbSurface = SDL_ConvertSurfaceFormat(rgbaSurface, SDL_PIXELFORMAT_RGB888, 0); if (!rgbSurface || SDL_SaveBMP(rgbSurface, filename.c_str())) { std::cerr << "SDL_SaveBMP failed: " << SDL_GetError() << std::endl; filename = ""; } SDL_FreeSurface(rgbSurface); SDL_FreeSurface(rgbaSurface); } else if (fileType == 2) { filename += ".ppm"; if (!Platform::WriteFile(screenshot->ToPPM(), filename)) { filename = ""; } } else { filename += ".png"; if (auto data = screenshot->ToPNG()) { if (!Platform::WriteFile(*data, filename)) filename = ""; } else filename = ""; } return filename; } int GameView::Record(bool record) { if (!record) { recording = false; recordingFolder = 0; } else if (!recording) { time_t startTime = time(NULL); recordingFolder = startTime; Platform::MakeDirectory("recordings"); Platform::MakeDirectory(ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder)); recording = true; recordingIndex = 0; } return recordingFolder; } void GameView::updateToolButtonScroll() { if (toolButtons.size()) { int x = currentMouse.X; int y = currentMouse.Y; int offsetDelta = 0; int newInitialX = WINDOWW - 56; int totalWidth = (toolButtons[0]->Size.X + 1) * toolButtons.size(); int scrollSize = (int)(((float)(XRES - BARSIZE))/((float)totalWidth) * ((float)XRES - BARSIZE)); if (scrollSize > XRES - 1) scrollSize = XRES - 1; if (totalWidth > XRES - 15) { int mouseX = x; float overflow = 0; float mouseLocation = 0; if (mouseX > XRES) mouseX = XRES; // if (mouseX < 15) // makes scrolling a little nicer at edges but apparently if you put hundreds of elements in a menu it makes the end not show ... // mouseX = 15; scrollBar->Visible = true; scrollBar->Position.X = (int)(((float)mouseX / (float)XRES) * (float)(XRES - scrollSize)) + 1; overflow = (float)(totalWidth - (XRES - BARSIZE)); mouseLocation = (float)(XRES - 3)/(float)((XRES - 2) - mouseX); // mouseLocation adjusted slightly in case you have 200 elements in one menu newInitialX += (int)(overflow/mouseLocation); } else { scrollBar->Visible = false; } scrollBar->Size.X = scrollSize - 1; offsetDelta = toolButtons[0]->Position.X - newInitialX; for (auto *button : toolButtons) { button->Position.X -= offsetDelta; } // Ensure that mouseLeave events are make their way to the buttons should they move from underneath the mouse pointer if (toolButtons[0]->Position.Y < y && toolButtons[0]->Position.Y + toolButtons[0]->Size.Y > y) { for (auto *button : toolButtons) { auto inside = button->Position.X < x && button->Position.X + button->Size.X > x; if (inside && !button->MouseInside) { button->MouseInside = true; button->OnMouseEnter(x, y); } if (!inside && button->MouseInside) { button->MouseInside = false; button->OnMouseLeave(x, y); } } } } } void GameView::OnMouseMove(int x, int y, int dx, int dy) { bool newMouseInZoom = c->MouseInZoom(ui::Point(x, y)); mousePosition = c->PointTranslate(ui::Point(x, y)); currentMouse = ui::Point(x, y); if (selectMode != SelectNone) { if (selectMode == PlaceSave) selectPoint1 = c->PointTranslate(ui::Point(x, y)); if (selectPoint1.X != -1) selectPoint2 = c->PointTranslate(ui::Point(x, y)); } else if (isMouseDown) { if (newMouseInZoom == mouseInZoom) { if (drawMode == DrawPoints) { currentPoint = mousePosition; c->DrawPoints(toolIndex, lastPoint, currentPoint, true); lastPoint = currentPoint; skipDraw = true; } else if (drawMode == DrawFill) { c->DrawFill(toolIndex, mousePosition); skipDraw = true; } } else if (drawMode == DrawPoints || drawMode == DrawFill) { isMouseDown = false; drawMode = DrawPoints; c->MouseUp(x, y, 0, GameController::mouseUpDrawEnd); } } mouseInZoom = newMouseInZoom; // set active menu (delayed) if (delayedActiveMenu != -1) { c->SetActiveMenu(delayedActiveMenu); delayedActiveMenu = -1; } updateToolButtonScroll(); } void GameView::OnMouseDown(int x, int y, unsigned button) { currentMouse = ui::Point(x, y); if (altBehaviour && !shiftBehaviour && !ctrlBehaviour) button = SDL_BUTTON_MIDDLE; if (!(zoomEnabled && !zoomCursorFixed)) { if (selectMode != SelectNone) { isMouseDown = true; if (button == SDL_BUTTON_LEFT && selectPoint1.X == -1) { selectPoint1 = c->PointTranslate(currentMouse); selectPoint2 = selectPoint1; } return; } if (currentMouse.X >= 0 && currentMouse.X < XRES && currentMouse.Y >= 0 && currentMouse.Y < YRES) { // update tool index, set new "last" tool so GameView can detect certain tools properly if (button == SDL_BUTTON_LEFT) toolIndex = 0; else if (button == SDL_BUTTON_RIGHT) toolIndex = 1; else if (button == SDL_BUTTON_MIDDLE) toolIndex = 2; else return; Tool *lastTool = c->GetActiveTool(toolIndex); c->SetLastTool(lastTool); windTool = lastTool->Identifier == "DEFAULT_UI_WIND"; decoBrush = lastTool->Identifier.BeginsWith("DEFAULT_DECOR_"); UpdateDrawMode(); isMouseDown = true; c->HistorySnapshot(); if (drawMode == DrawRect || drawMode == DrawLine) { drawPoint1 = c->PointTranslate(currentMouse); } else if (drawMode == DrawPoints) { lastPoint = currentPoint = c->PointTranslate(currentMouse); c->DrawPoints(toolIndex, lastPoint, currentPoint, false); } else if (drawMode == DrawFill) { c->DrawFill(toolIndex, c->PointTranslate(currentMouse)); } } } } Vec2 GameView::PlaceSavePos() const { auto [ trQuoX, trRemX ] = floorDiv(placeSaveTranslate.X, CELL); auto [ trQuoY, trRemY ] = floorDiv(placeSaveTranslate.Y, CELL); auto usefulSize = placeSaveThumb->Size(); if (trRemX) usefulSize.X -= CELL; if (trRemY) usefulSize.Y -= CELL; auto cursorCell = (usefulSize - Vec2{ CELL, CELL }) / 2 - Vec2{ trQuoX, trQuoY } * CELL; // stamp coordinates auto unaligned = selectPoint2 - cursorCell; auto quoX = floorDiv(unaligned.X, CELL).first; auto quoY = floorDiv(unaligned.Y, CELL).first; return { quoX, quoY }; } void GameView::OnMouseUp(int x, int y, unsigned button) { currentMouse = ui::Point(x, y); if (zoomEnabled && !zoomCursorFixed) { zoomCursorFixed = true; drawMode = DrawPoints; isMouseDown = false; } else if (isMouseDown) { isMouseDown = false; if (selectMode != SelectNone) { if (button == SDL_BUTTON_LEFT && selectPoint1.X != -1 && selectPoint1.Y != -1 && selectPoint2.X != -1 && selectPoint2.Y != -1) { if (selectMode == PlaceSave) { if (placeSaveThumb && y <= WINDOWH-BARSIZE) { c->PlaceSave(PlaceSavePos()); } } else { int x2 = (selectPoint1.X>selectPoint2.X) ? selectPoint1.X : selectPoint2.X; int y2 = (selectPoint1.Y>selectPoint2.Y) ? selectPoint1.Y : selectPoint2.Y; int x1 = (selectPoint2.XCopyRegion(ui::Point(x1, y1), ui::Point(x2, y2)); else if (selectMode == SelectCut) c->CutRegion(ui::Point(x1, y1), ui::Point(x2, y2)); else if (selectMode == SelectStamp) c->StampRegion(ui::Point(x1, y1), ui::Point(x2, y2)); } } selectMode = SelectNone; return; } ui::Point finalDrawPoint2 = c->PointTranslate(currentMouse); if (drawMode == DrawRect || drawMode == DrawLine) { drawPoint2 = finalDrawPoint2; if (drawSnap && drawMode == DrawLine) { finalDrawPoint2 = lineSnapCoords(c->PointTranslate(drawPoint1), drawPoint2); } if (drawSnap && drawMode == DrawRect) { finalDrawPoint2 = rectSnapCoords(c->PointTranslate(drawPoint1), drawPoint2); } if (drawMode == DrawRect) { c->DrawRect(toolIndex, c->PointTranslate(drawPoint1), finalDrawPoint2); } if (drawMode == DrawLine) { c->DrawLine(toolIndex, c->PointTranslate(drawPoint1), finalDrawPoint2); } } else if (drawMode == DrawPoints) { // draw final line c->DrawPoints(toolIndex, lastPoint, finalDrawPoint2, true); // plop tool stuff (like STKM) c->ToolClick(toolIndex, finalDrawPoint2); } else if (drawMode == DrawFill) { c->DrawFill(toolIndex, finalDrawPoint2); } } // this shouldn't happen, but do this just in case else if (selectMode != SelectNone && button != SDL_BUTTON_LEFT) selectMode = SelectNone; // update the drawing mode for the next line // since ctrl/shift state may have changed since we started drawing UpdateDrawMode(); } void GameView::ToolTip(ui::Point senderPosition, String toolTip) { // buttom button tooltips if (senderPosition.Y > Size.Y-17) { if (selectMode == PlaceSave || selectMode == SelectNone) { buttonTip = toolTip; isButtonTipFadingIn = true; } } // quickoption and menu tooltips else if(senderPosition.X > Size.X-BARSIZE)// < Size.Y-(quickOptionButtons.size()+1)*16) { this->toolTip = toolTip; toolTipPosition = ui::Point(Size.X-27-(Graphics::TextSize(toolTip).X - 1), senderPosition.Y+3); if(toolTipPosition.Y+10 > Size.Y-MENUSIZE) toolTipPosition = ui::Point(Size.X-27-(Graphics::TextSize(toolTip).X - 1), Size.Y-MENUSIZE-10); isToolTipFadingIn = true; } // element tooltips else { this->toolTip = toolTip; toolTipPosition = ui::Point(Size.X-27-(Graphics::TextSize(toolTip).X - 1), Size.Y-MENUSIZE-10); isToolTipFadingIn = true; } } void GameView::OnMouseWheel(int x, int y, int d) { if (!d) return; if (selectMode != SelectNone) { return; } if (zoomEnabled && !zoomCursorFixed) { c->AdjustZoomSize(d); } else { c->AdjustBrushSize(d, false, ctrlBehaviour, shiftBehaviour); } } void GameView::BeginStampSelection() { selectMode = SelectStamp; selectPoint1 = selectPoint2 = ui::Point(-1, -1); isMouseDown = false; buttonTip = "\x0F\xEF\xEF\020Click-and-drag to specify an area to create a stamp (right click = cancel)"; buttonTipShow = 120; } void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) { if (introText > 50) { introText = 50; } if (selectMode != SelectNone) { if (selectMode == PlaceSave) { switch (key) { case SDLK_RIGHT: TranslateSave({ 1, 0 }); return; case SDLK_LEFT: TranslateSave({ -1, 0 }); return; case SDLK_UP: TranslateSave({ 0, -1 }); return; case SDLK_DOWN: TranslateSave({ 0, 1 }); return; } if (scan == SDL_SCANCODE_R && !repeat) { if (ctrl && shift) { //Vertical flip TransformSave(Mat2::MirrorY); } else if (!ctrl && shift) { //Horizontal flip TransformSave(Mat2::MirrorX); } else { //Rotate 90deg TransformSave(Mat2::CCW); } return; } } } if (repeat) return; bool didKeyShortcut = true; switch(scan) { case SDL_SCANCODE_GRAVE: c->ShowConsole(); break; case SDL_SCANCODE_SPACE: //Space c->SetPaused(); break; case SDL_SCANCODE_Z: if (selectMode != SelectNone && isMouseDown) break; if (ctrl && !isMouseDown) { if (shift) c->HistoryForward(); else c->HistoryRestore(); } else { isMouseDown = false; zoomCursorFixed = false; c->SetZoomEnabled(true); } break; case SDL_SCANCODE_P: case SDL_SCANCODE_F2: if (ctrl) { if (shift) c->SetActiveTool(1, "DEFAULT_UI_PROPERTY"); else c->SetActiveTool(0, "DEFAULT_UI_PROPERTY"); } else doScreenshot = true; break; case SDL_SCANCODE_F3: SetDebugHUD(!GetDebugHUD()); break; case SDL_SCANCODE_F5: c->ReloadSim(); break; case SDL_SCANCODE_A: if (Client::Ref().GetAuthUser().UserElevation != User::ElevationNone && ctrl) { ByteString authorString = Client::Ref().GetAuthorInfo().toStyledString(); new InformationMessage("Save authorship info", authorString.FromUtf8(), true); } break; case SDL_SCANCODE_R: if (ctrl) c->ReloadSim(); break; case SDL_SCANCODE_E: if (ctrl) c->SetEdgeMode(c->GetEdgeMode() + 1); else c->OpenElementSearch(); break; case SDL_SCANCODE_F: if (ctrl) { auto findingElementCandidate = FindingElementCandidate(); if (ren->findingElement == findingElementCandidate) { ren->findingElement = std::nullopt; } else { ren->findingElement = findingElementCandidate; } } else c->FrameStep(); break; case SDL_SCANCODE_G: if (ctrl) c->ShowGravityGrid(); else if(shift) c->AdjustGridSize(-1); else c->AdjustGridSize(1); break; case SDL_SCANCODE_F1: if(!introText) introText = 8047; else introText = 0; break; case SDL_SCANCODE_F11: ui::Engine::Ref().SetFullscreen(!ui::Engine::Ref().GetFullscreen()); break; case SDL_SCANCODE_H: if(ctrl) { if(!introText) introText = 8047; else introText = 0; } else showHud = !showHud; break; case SDL_SCANCODE_B: if(ctrl) c->SetDecoration(); else if (colourPicker->GetParentWindow()) c->SetActiveMenu(lastMenu); else { c->SetDecoration(true); c->SetPaused(true); c->SetActiveMenu(SC_DECO); } break; case SDL_SCANCODE_Y: if (ctrl) { c->HistoryForward(); } else { c->SwitchAir(); } break; case SDL_SCANCODE_ESCAPE: case SDL_SCANCODE_Q: if (ALLOW_QUIT) { ui::Engine::Ref().ConfirmExit(); } break; case SDL_SCANCODE_U: if (ctrl) c->ResetAHeat(); else c->ToggleAHeat(); break; case SDL_SCANCODE_N: c->ToggleNewtonianGravity(); break; case SDL_SCANCODE_EQUALS: if(ctrl) c->ResetSpark(); else c->ResetAir(); break; case SDL_SCANCODE_C: if(ctrl) { selectMode = SelectCopy; selectPoint1 = selectPoint2 = ui::Point(-1, -1); isMouseDown = false; buttonTip = "\x0F\xEF\xEF\020Click-and-drag to specify an area to copy (right click = cancel)"; buttonTipShow = 120; } break; case SDL_SCANCODE_X: if(ctrl) { selectMode = SelectCut; selectPoint1 = selectPoint2 = ui::Point(-1, -1); isMouseDown = false; buttonTip = "\x0F\xEF\xEF\020Click-and-drag to specify an area to copy then cut (right click = cancel)"; buttonTipShow = 120; } break; case SDL_SCANCODE_V: if (ctrl) { if (c->LoadClipboard()) { selectPoint1 = selectPoint2 = mousePosition; isMouseDown = false; } } break; case SDL_SCANCODE_L: { auto &stampIDs = Client::Ref().GetStamps(); if (stampIDs.size()) { auto saveFile = Client::Ref().GetStamp(stampIDs[0]); if (!saveFile || !saveFile->GetGameSave()) break; c->LoadStamp(saveFile->TakeGameSave()); selectPoint1 = selectPoint2 = mousePosition; isMouseDown = false; break; } } case SDL_SCANCODE_K: selectMode = SelectNone; selectPoint1 = selectPoint2 = ui::Point(-1, -1); c->OpenStamps(); break; case SDL_SCANCODE_RIGHTBRACKET: if(zoomEnabled && !zoomCursorFixed) c->AdjustZoomSize(1, !alt); else c->AdjustBrushSize(1, !alt, ctrlBehaviour, shiftBehaviour); break; case SDL_SCANCODE_LEFTBRACKET: if(zoomEnabled && !zoomCursorFixed) c->AdjustZoomSize(-1, !alt); else c->AdjustBrushSize(-1, !alt, ctrlBehaviour, shiftBehaviour); break; case SDL_SCANCODE_I: if(ctrl) c->Install(); else c->InvertAirSim(); break; case SDL_SCANCODE_SEMICOLON: if (ctrl) c->SetReplaceModeFlags(c->GetReplaceModeFlags()^SPECIFIC_DELETE); else c->SetReplaceModeFlags(c->GetReplaceModeFlags()^REPLACE_MODE); break; default: didKeyShortcut = false; } if (!didKeyShortcut) { switch (key) { case SDLK_AC_BACK: if (ALLOW_QUIT) { ui::Engine::Ref().ConfirmExit(); } break; case SDLK_TAB: //Tab c->ChangeBrush(); break; case SDLK_INSERT: if (ctrl) c->SetReplaceModeFlags(c->GetReplaceModeFlags()^SPECIFIC_DELETE); else c->SetReplaceModeFlags(c->GetReplaceModeFlags()^REPLACE_MODE); break; case SDLK_DELETE: c->SetReplaceModeFlags(c->GetReplaceModeFlags()^SPECIFIC_DELETE); break; } } if (shift && showDebug && key == '1') c->LoadRenderPreset(10); else if (key >= '0' && key <= '9') { c->LoadRenderPreset(key-'0'); } } void GameView::OnKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) { if (repeat) return; if (scan == SDL_SCANCODE_Z) { if (!zoomCursorFixed && !alt) c->SetZoomEnabled(false); return; } } void GameView::OnBlur() { disableAltBehaviour(); disableCtrlBehaviour(); disableShiftBehaviour(); isMouseDown = false; drawMode = DrawPoints; c->Blur(); } void GameView::OnFileDrop(ByteString filename) { if (!(filename.EndsWith(".cps") || filename.EndsWith(".stm"))) { new ErrorMessage("Error loading save", "Dropped file is not a TPT save file (.cps or .stm format)"); return; } if (filename.EndsWith(".stm")) { auto saveFile = Client::Ref().GetStamp(filename); if (!saveFile || !saveFile->GetGameSave()) { new ErrorMessage("Error loading stamp", "Dropped stamp could not be loaded: " + saveFile->GetError()); return; } c->LoadStamp(saveFile->TakeGameSave()); } else { auto saveFile = Client::Ref().LoadSaveFile(filename); if (!saveFile) return; if (saveFile->GetError().length()) { new ErrorMessage("Error loading save", "Dropped save file could not be loaded: " + saveFile->GetError()); return; } c->LoadSaveFile(std::move(saveFile)); } // hide the info text if it's not already hidden SkipIntroText(); } void GameView::SkipIntroText() { introText = 0; } void GameView::OnTick(float dt) { if (selectMode == PlaceSave && !placeSaveThumb) selectMode = SelectNone; if (zoomEnabled && !zoomCursorFixed) c->SetZoomPosition(currentMouse); if (skipDraw) { skipDraw = false; } else if (selectMode == SelectNone && isMouseDown) { if (drawMode == DrawPoints) { c->DrawPoints(toolIndex, lastPoint, currentPoint, true); lastPoint = currentPoint; } else if (drawMode == DrawFill) { c->DrawFill(toolIndex, c->PointTranslate(currentMouse)); } else if (windTool && drawMode == DrawLine) { ui::Point drawPoint2 = currentMouse; if (altBehaviour) drawPoint2 = lineSnapCoords(c->PointTranslate(drawPoint1), currentMouse); c->DrawLine(toolIndex, c->PointTranslate(drawPoint1), c->PointTranslateNoClamp(drawPoint2)); } } int foundSignID = c->GetSignAt(mousePosition.X, mousePosition.Y); if (foundSignID != -1) { String str = c->GetSignText(foundSignID); auto si = c->GetSignSplit(foundSignID); StringBuilder tooltip; switch (si.second) { case sign::Type::Save: tooltip << "Go to save ID:" << str.Substr(3, si.first - 3); break; case sign::Type::Thread: tooltip << "Open forum thread " << str.Substr(3, si.first - 3) << " in browser"; break; case sign::Type::Search: tooltip << "Search for " << str.Substr(3, si.first - 3); break; default: break; } if (tooltip.Size()) { ToolTip(ui::Point(0, Size.Y), tooltip.Build()); } } if(introText) { introText -= int(dt)>0?(int(dt) < 5? int(dt):5):1; if(introText < 0) introText = 0; } if(infoTipPresence>0) { infoTipPresence -= int(dt)>0?int(dt):1; if(infoTipPresence<0) infoTipPresence = 0; } if (isButtonTipFadingIn || (selectMode != PlaceSave && selectMode != SelectNone)) { isButtonTipFadingIn = false; if(buttonTipShow < 120) { buttonTipShow += int(dt*2)>0?int(dt*2):1; if(buttonTipShow>120) buttonTipShow = 120; } } else if(buttonTipShow>0) { buttonTipShow -= int(dt)>0?int(dt):1; if(buttonTipShow<0) buttonTipShow = 0; } if (isToolTipFadingIn) { isToolTipFadingIn = false; if(toolTipPresence < 120) { toolTipPresence += int(dt*2)>0?int(dt*2):1; if(toolTipPresence>120) toolTipPresence = 120; } } else if(toolTipPresence>0) { toolTipPresence -= int(dt)>0?int(dt):1; if(toolTipPresence<0) toolTipPresence = 0; } c->Update(); } void GameView::DoMouseMove(int x, int y, int dx, int dy) { if(c->MouseMove(x, y, dx, dy)) Window::DoMouseMove(x, y, dx, dy); } void GameView::DoMouseDown(int x, int y, unsigned button) { if(introText > 50) introText = 50; if(c->MouseDown(x, y, button)) Window::DoMouseDown(x, y, button); } void GameView::DoMouseUp(int x, int y, unsigned button) { if(c->MouseUp(x, y, button, GameController::mouseUpNormal)) Window::DoMouseUp(x, y, button); } void GameView::DoMouseWheel(int x, int y, int d) { if(c->MouseWheel(x, y, d)) Window::DoMouseWheel(x, y, d); } void GameView::DoTextInput(String text) { if (c->TextInput(text)) Window::DoTextInput(text); } void GameView::DoTextEditing(String text) { if (c->TextEditing(text)) Window::DoTextEditing(text); } void GameView::DoKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) { if (shift && !shiftBehaviour) enableShiftBehaviour(); if (ctrl && !ctrlBehaviour) enableCtrlBehaviour(); if (alt && !altBehaviour) enableAltBehaviour(); if (c->KeyPress(key, scan, repeat, shift, ctrl, alt)) Window::DoKeyPress(key, scan, repeat, shift, ctrl, alt); } void GameView::DoKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) { if (!shift && shiftBehaviour) disableShiftBehaviour(); if (!ctrl && ctrlBehaviour) disableCtrlBehaviour(); if (!alt && altBehaviour) disableAltBehaviour(); if (c->KeyRelease(key, scan, repeat, shift, ctrl, alt)) Window::DoKeyRelease(key, scan, repeat, shift, ctrl, alt); } void GameView::DoExit() { Window::DoExit(); c->Exit(); } void GameView::DoDraw() { Window::DoDraw(); constexpr std::array fadeout = { { // * Gamma-corrected. 255, 195, 145, 103, 69, 42, 23, 10, 3 } }; auto *g = GetGraphics(); for (auto x = 0; x < int(fadeout.size()); ++x) { g->BlendLine({ x, YRES + 1 }, { x, YRES + 18 }, 0x000000_rgb .WithAlpha(fadeout[x])); g->BlendLine({ XRES - x, YRES + 1 }, { XRES - x, YRES + 18 }, 0x000000_rgb .WithAlpha(fadeout[x])); } c->Tick(); { auto rect = g->Size().OriginRect(); g->SwapClipRect(rect); // reset any nonsense cliprect Lua left configured } } void GameView::NotifyNotificationsChanged(GameModel * sender) { for (auto *notificationComponent : notificationComponents) { RemoveComponent(notificationComponent); delete notificationComponent; } notificationComponents.clear(); std::vector notifications = sender->GetNotifications(); int currentY = YRES-23; for (auto *notification : notifications) { int width = Graphics::TextSize(notification->Message).X + 7; ui::Button * tempButton = new ui::Button(ui::Point(XRES-width-22, currentY), ui::Point(width, 15), notification->Message); tempButton->SetActionCallback({ [notification] { notification->Action(); } }); tempButton->Appearance.BorderInactive = style::Colour::WarningTitle; tempButton->Appearance.TextInactive = style::Colour::WarningTitle; tempButton->Appearance.BorderHover = ui::Colour(255, 175, 0); tempButton->Appearance.TextHover = ui::Colour(255, 175, 0); AddComponent(tempButton); notificationComponents.push_back(tempButton); tempButton = new ui::Button(ui::Point(XRES-20, currentY), ui::Point(15, 15), 0xE02A); //tempButton->SetIcon(IconClose); auto closeNotification = [this, notification] { c->RemoveNotification(notification); }; tempButton->SetActionCallback({ closeNotification, closeNotification }); tempButton->Appearance.Margin.Left -= 1; tempButton->Appearance.Margin.Top -= 1; tempButton->Appearance.BorderInactive = style::Colour::WarningTitle; tempButton->Appearance.TextInactive = style::Colour::WarningTitle; tempButton->Appearance.BorderHover = ui::Colour(255, 175, 0); tempButton->Appearance.TextHover = ui::Colour(255, 175, 0); AddComponent(tempButton); notificationComponents.push_back(tempButton); currentY -= 17; } } void GameView::NotifyZoomChanged(GameModel * sender) { zoomEnabled = sender->GetZoomEnabled(); } void GameView::NotifyLogChanged(GameModel * sender, String entry) { logEntries.push_front(std::pair(entry, 600)); if (logEntries.size() > 20) logEntries.pop_back(); } void GameView::NotifyPlaceSaveChanged(GameModel * sender) { placeSaveTransform = Mat2::Identity; placeSaveTranslate = Vec2::Zero; ApplyTransformPlaceSave(); } void GameView::TranslateSave(Vec2 addToTranslate) { placeSaveTranslate += addToTranslate; ApplyTransformPlaceSave(); } void GameView::TransformSave(Mat2 mulToTransform) { placeSaveTranslate = Vec2::Zero; // reset offset placeSaveTransform = mulToTransform * placeSaveTransform; ApplyTransformPlaceSave(); } void GameView::ApplyTransformPlaceSave() { auto remX = floorDiv(placeSaveTranslate.X, CELL).second; auto remY = floorDiv(placeSaveTranslate.Y, CELL).second; c->TransformPlaceSave(placeSaveTransform, { remX, remY }); } void GameView::NotifyTransformedPlaceSaveChanged(GameModel *sender) { if (sender->GetTransformedPlaceSave()) { placeSaveThumb = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRenderer()); selectMode = PlaceSave; selectPoint2 = mousePosition; } else { placeSaveThumb.reset(); selectMode = SelectNone; } } void GameView::enableShiftBehaviour() { if (!shiftBehaviour) { shiftBehaviour = true; if (!isMouseDown || selectMode != SelectNone) UpdateDrawMode(); UpdateToolStrength(); } } void GameView::disableShiftBehaviour() { if (shiftBehaviour) { shiftBehaviour = false; if (!isMouseDown || selectMode != SelectNone) UpdateDrawMode(); UpdateToolStrength(); } } void GameView::enableAltBehaviour() { if (!altBehaviour) { altBehaviour = true; drawSnap = true; } } void GameView::disableAltBehaviour() { if (altBehaviour) { altBehaviour = false; drawSnap = false; } } void GameView::enableCtrlBehaviour() { if (!ctrlBehaviour) { ctrlBehaviour = true; if (!isMouseDown || selectMode != SelectNone) UpdateDrawMode(); UpdateToolStrength(); //Show HDD save & load buttons saveSimulationButton->Appearance.BackgroundInactive = saveSimulationButton->Appearance.BackgroundHover = ui::Colour(255, 255, 255); saveSimulationButton->Appearance.TextInactive = saveSimulationButton->Appearance.TextHover = ui::Colour(0, 0, 0); saveSimulationButton->Enabled = true; SetSaveButtonTooltips(); searchButton->Appearance.BackgroundInactive = searchButton->Appearance.BackgroundHover = ui::Colour(255, 255, 255); searchButton->Appearance.TextInactive = searchButton->Appearance.TextHover = ui::Colour(0, 0, 0); searchButton->SetToolTip("Open a simulation from your hard drive."); if (currentSaveType == 2) saveSimulationButton->SetShowSplit(true); } } void GameView::disableCtrlBehaviour() { if (ctrlBehaviour) { ctrlBehaviour = false; if (!isMouseDown || selectMode != SelectNone) UpdateDrawMode(); UpdateToolStrength(); //Hide HDD save & load buttons saveSimulationButton->Appearance.BackgroundInactive = ui::Colour(0, 0, 0); saveSimulationButton->Appearance.BackgroundHover = ui::Colour(20, 20, 20); saveSimulationButton->Appearance.TextInactive = saveSimulationButton->Appearance.TextHover = ui::Colour(255, 255, 255); saveSimulationButton->Enabled = saveSimulationButtonEnabled && saveReuploadAllowed; SetSaveButtonTooltips(); searchButton->Appearance.BackgroundInactive = ui::Colour(0, 0, 0); searchButton->Appearance.BackgroundHover = ui::Colour(20, 20, 20); searchButton->Appearance.TextInactive = searchButton->Appearance.TextHover = ui::Colour(255, 255, 255); searchButton->SetToolTip("Find & open a simulation. Hold Ctrl to load offline saves."); if (currentSaveType == 2) saveSimulationButton->SetShowSplit(false); } } void GameView::UpdateDrawMode() { if (ctrlBehaviour && shiftBehaviour) { if (toolBrush) drawMode = DrawPoints; else drawMode = DrawFill; } else if (ctrlBehaviour) drawMode = DrawRect; else if (shiftBehaviour) drawMode = DrawLine; else drawMode = DrawPoints; // TODO: have tools decide on draw mode if (c->GetLastTool() && c->GetLastTool()->Identifier == "DEFAULT_UI_SAMPLE") { drawMode = DrawPoints; } } void GameView::UpdateToolStrength() { if (shiftBehaviour) c->SetToolStrength(10.0f); else if (ctrlBehaviour) c->SetToolStrength(.1f); else c->SetToolStrength(1.0f); } void GameView::SetSaveButtonTooltips() { if (!Client::Ref().GetAuthUser().UserID) saveSimulationButton->SetToolTips("Overwrite the open simulation on your hard drive.", "Save the simulation to your hard drive. Login to save online."); else if (ctrlBehaviour) saveSimulationButton->SetToolTips("Overwrite the open simulation on your hard drive.", "Save the simulation to your hard drive."); else if (saveSimulationButton->GetShowSplit()) saveSimulationButton->SetToolTips("Re-upload the current simulation", "Modify simulation properties"); else saveSimulationButton->SetToolTips("Re-upload the current simulation", "Upload a new simulation. Hold Ctrl to save offline."); } void GameView::OnDraw() { Graphics * g = GetGraphics(); if (ren) { // we're the main thread, we may write graphicscache auto &sd = SimulationData::Ref(); std::unique_lock lk(sd.elementGraphicsMx); ren->clearScreen(); ren->draw_air(); c->BeforeSimDraw(); ren->RenderBegin(); ren->SetSample(c->PointTranslate(currentMouse)); if (showBrush && selectMode == SelectNone && (!zoomEnabled || zoomCursorFixed) && activeBrush && (isMouseDown || (currentMouse.X >= 0 && currentMouse.X < XRES && currentMouse.Y >= 0 && currentMouse.Y < YRES))) { ui::Point finalCurrentMouse = windTool ? c->PointTranslateNoClamp(currentMouse) : c->PointTranslate(currentMouse); ui::Point initialDrawPoint = drawPoint1; if (wallBrush) { finalCurrentMouse = c->NormaliseBlockCoord(finalCurrentMouse); initialDrawPoint = c->NormaliseBlockCoord(initialDrawPoint); } if (drawMode == DrawRect && isMouseDown) { if (drawSnap) { finalCurrentMouse = rectSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); } if (wallBrush) { if (finalCurrentMouse.X > initialDrawPoint.X) finalCurrentMouse.X += CELL-1; else initialDrawPoint.X += CELL-1; if (finalCurrentMouse.Y > initialDrawPoint.Y) finalCurrentMouse.Y += CELL-1; else initialDrawPoint.Y += CELL-1; } activeBrush->RenderRect(ren, c->PointTranslate(initialDrawPoint), finalCurrentMouse); } else if (drawMode == DrawLine && isMouseDown) { if (drawSnap) { finalCurrentMouse = lineSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); } activeBrush->RenderLine(ren, c->PointTranslate(initialDrawPoint), finalCurrentMouse); } else if (drawMode == DrawFill)// || altBehaviour) { if (!decoBrush) activeBrush->RenderFill(ren, finalCurrentMouse); } if (drawMode == DrawPoints || drawMode==DrawLine || (drawMode == DrawRect && !isMouseDown)) { if (wallBrush) { ui::Point finalBrushRadius = c->NormaliseBlockCoord(activeBrush->GetRadius()); auto topLeft = finalCurrentMouse - finalBrushRadius; auto bottomRight = finalCurrentMouse + finalBrushRadius + Vec2{ CELL - 1, CELL - 1 }; ren->XorLine({ topLeft.X, topLeft.Y }, { bottomRight.X, topLeft.Y }); ren->XorLine({ topLeft.X, bottomRight.Y }, { bottomRight.X, bottomRight.Y }); ren->XorLine({ topLeft.X, topLeft.Y + 1 }, { topLeft.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice ren->XorLine({ bottomRight.X, topLeft.Y + 1 }, { bottomRight.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice } else { activeBrush->RenderPoint(ren, finalCurrentMouse); } } } if(selectMode!=SelectNone) { if(selectMode==PlaceSave) { if(placeSaveThumb && selectPoint2.X!=-1) { auto rect = RectSized(PlaceSavePos() * CELL, placeSaveThumb->Size()); ren->BlendImage(placeSaveThumb->Data(), 0x80, rect); ren->XorDottedRect(rect); } } else { if(selectPoint1.X==-1) { ren->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, YRES }), 0x000000_rgb .WithAlpha(100)); } else { int x2 = (selectPoint1.X>selectPoint2.X)?selectPoint1.X:selectPoint2.X; int y2 = (selectPoint1.Y>selectPoint2.Y)?selectPoint1.Y:selectPoint2.Y; int x1 = (selectPoint2.XXRES-1) x2 = XRES-1; if(y2>YRES-1) y2 = YRES-1; ren->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, y1 }), 0x000000_rgb .WithAlpha(100)); ren->BlendFilledRect(RectSized(Vec2{ 0, y2+1 }, Vec2{ XRES, YRES-y2-1 }), 0x000000_rgb .WithAlpha(100)); ren->BlendFilledRect(RectSized(Vec2{ 0, y1 }, Vec2{ x1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); ren->BlendFilledRect(RectSized(Vec2{ x2+1, y1 }, Vec2{ XRES-x2-1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); ren->XorDottedRect(RectBetween(Vec2{ x1, y1 }, Vec2{ x2, y2 })); } } } c->AfterSimDraw(); ren->RenderEnd(); std::copy_n(ren->Data(), ren->Size().X * ren->Size().Y, g->Data()); if (doScreenshot) { doScreenshot = false; TakeScreenshot(0, 0); } if(recording) { std::vector data = ren->DumpFrame().ToPPM(); ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm"); Platform::WriteFile(data, filename); } if (logEntries.size()) { int startX = 20; int startY = YRES-20; std::deque >::iterator iter; for(iter = logEntries.begin(); iter != logEntries.end(); iter++) { String message = (*iter).first; int alpha = std::min((*iter).second, 255); if (alpha <= 0) //erase this and everything older { logEntries.erase(iter, logEntries.end()); break; } startY -= 14; g->BlendFilledRect(RectSized(Vec2{ startX-3, startY-3 }, Vec2{ Graphics::TextSize(message).X + 5, 14 }), 0x000000_rgb .WithAlpha(std::min(100, alpha))); g->BlendText({ startX, startY }, message, 0xFFFFFF_rgb .WithAlpha(alpha)); (*iter).second -= 3; } } } if (recording) { String sampleInfo = String::Build("#", screenshotIndex, " ", String(0xE00E), " REC"); int textWidth = Graphics::TextSize(sampleInfo).X - 1; g->BlendFilledRect(RectSized(Vec2{ XRES-20-textWidth, 12 }, Vec2{ textWidth+8, 15 }), 0x000000_rgb .WithAlpha(127)); g->BlendText({ XRES-16-textWidth, 16 }, sampleInfo, 0xFF3214_rgb .WithAlpha(255)); } else if(showHud) { //Draw info about simulation under cursor int wavelengthGfx = 0; int alpha = 255-introText*5; if (toolTipPosition.Y < 120) alpha -= toolTipPresence*3; if (alpha < 0) alpha = 0; StringBuilder sampleInfo; sampleInfo << Format::Precision(2); int type = sample.particle.type; if (type) { int ctype = sample.particle.ctype; if (type == PT_PHOT || type == PT_BIZR || type == PT_BIZRG || type == PT_BIZRS || type == PT_FILT || type == PT_BRAY || type == PT_C5) wavelengthGfx = (ctype&0x3FFFFFFF); if (showDebug) { if (type == PT_LAVA && c->IsValidElement(ctype)) { sampleInfo << "Molten " << c->ElementResolve(ctype, 0); } else if ((type == PT_PIPE || type == PT_PPIP) && c->IsValidElement(ctype)) { if (ctype == PT_LAVA && c->IsValidElement(sample.particle.tmp4)) { sampleInfo << c->ElementResolve(type, 0) << " with molten " << c->ElementResolve(sample.particle.tmp4, -1); } else { sampleInfo << c->ElementResolve(type, 0) << " with " << c->ElementResolve(ctype, sample.particle.tmp4); } } else if (type == PT_LIFE) { sampleInfo << c->ElementResolve(type, ctype); } else if (type == PT_FILT) { sampleInfo << c->ElementResolve(type, ctype); String filtModes[] = {"set colour", "AND", "OR", "subtract colour", "red shift", "blue shift", "no effect", "XOR", "NOT", "old QRTZ scattering", "variable red shift", "variable blue shift"}; if (sample.particle.tmp>=0 && sample.particle.tmp<=11) sampleInfo << " (" << filtModes[sample.particle.tmp] << ")"; else sampleInfo << " (unknown mode)"; } else { sampleInfo << c->ElementResolve(type, ctype); if (wavelengthGfx || type == PT_EMBR || type == PT_PRTI || type == PT_PRTO) { // Do nothing, ctype is meaningless for these elements } // Some elements store extra LIFE info in upper bits of ctype, instead of tmp/tmp2 else if (type == PT_CRAY || type == PT_DRAY || type == PT_CONV || type == PT_LDTC) sampleInfo << " (" << c->ElementResolve(TYP(ctype), ID(ctype)) << ")"; else if (type == PT_CLNE || type == PT_BCLN || type == PT_PCLN || type == PT_PBCN || type == PT_DTEC) sampleInfo << " (" << c->ElementResolve(ctype, sample.particle.tmp) << ")"; else if (c->IsValidElement(ctype) && type != PT_GLOW && type != PT_WIRE && type != PT_SOAP && type != PT_LITH) sampleInfo << " (" << c->ElementResolve(ctype, 0) << ")"; else if (ctype) sampleInfo << " (" << ctype << ")"; } sampleInfo << ", Temp: "; format::RenderTemperature(sampleInfo, sample.particle.temp, c->GetTemperatureScale()); sampleInfo << ", Life: " << sample.particle.life; if (sample.particle.type != PT_RFRG && sample.particle.type != PT_RFGL && sample.particle.type != PT_LIFE) { if (sample.particle.type == PT_CONV) { String elemName = c->ElementResolve( TYP(sample.particle.tmp), ID(sample.particle.tmp)); if (elemName == "") sampleInfo << ", Tmp: " << sample.particle.tmp; else sampleInfo << ", Tmp: " << elemName; } else sampleInfo << ", Tmp: " << sample.particle.tmp; } // only elements that use .tmp2 show it in the debug HUD if (type == PT_CRAY || type == PT_DRAY || type == PT_EXOT || type == PT_LIGH || type == PT_SOAP || type == PT_TRON || type == PT_VIBR || type == PT_VIRS || type == PT_WARP || type == PT_LCRY || type == PT_CBNW || type == PT_TSNS || type == PT_DTEC || type == PT_LSNS || type == PT_PSTN || type == PT_LDTC || type == PT_VSNS || type == PT_LITH || type == PT_CONV || type == PT_ETRD) sampleInfo << ", Tmp2: " << sample.particle.tmp2; sampleInfo << ", Pressure: " << sample.AirPressure; } else { sampleInfo << c->BasicParticleInfo(sample.particle); sampleInfo << ", Temp: "; format::RenderTemperature(sampleInfo, sample.particle.temp, c->GetTemperatureScale()); sampleInfo << ", Pressure: " << sample.AirPressure; } } else if (sample.WallType) { sampleInfo << c->WallName(sample.WallType); sampleInfo << ", Pressure: " << sample.AirPressure; } else if (sample.isMouseInSim) { sampleInfo << "Empty, Pressure: " << sample.AirPressure; } else { sampleInfo << "Empty"; } int textWidth = Graphics::TextSize(sampleInfo.Build()).X - 1; g->BlendFilledRect(RectSized(Vec2{ XRES-20-textWidth, 12 }, Vec2{ textWidth+8, 15 }), 0x000000_rgb .WithAlpha(int(alpha*0.5f))); g->BlendText({ XRES-16-textWidth, 16 }, sampleInfo.Build(), 0xFFFFFF_rgb .WithAlpha(int(alpha*0.75f))); if (wavelengthGfx) { int i, cr, cg, cb, j, h = 3, x = XRES-19-textWidth, y = 10; int tmp; g->BlendFilledRect(RectSized(Vec2{ x, y }, Vec2{ 30, h }), 0x404040_rgb .WithAlpha(alpha)); for (i = 0; i < 30; i++) { if ((wavelengthGfx >> i)&1) { // Need a spread of wavelengths to get a smooth spectrum, 5 bits seems to work reasonably well if (i>2) tmp = 0x1F << (i-2); else tmp = 0x1F >> (2-i); cg = 0; cb = 0; cr = 0; for (j=0; j<12; j++) { cr += (tmp >> (j+18)) & 1; cb += (tmp >> j) & 1; } for (j=0; j<13; j++) cg += (tmp >> (j+9)) & 1; tmp = 624/(cr+cg+cb+1); cr *= tmp; cg *= tmp; cb *= tmp; for (j=0; jBlendPixel({ x+29-i, y+j }, RGBA(cr>255?255:cr, cg>255?255:cg, cb>255?255:cb, alpha)); } } } if (showDebug) { StringBuilder sampleInfo; sampleInfo << Format::Precision(2); if (type) sampleInfo << "#" << sample.ParticleID << ", "; sampleInfo << "X:" << sample.PositionX << " Y:" << sample.PositionY; if (sample.Gravity) sampleInfo << ", GX: " << sample.GravityVelocityX << " GY: " << sample.GravityVelocityY; if (c->GetAHeatEnable()) { sampleInfo << ", AHeat: "; format::RenderTemperature(sampleInfo, sample.AirTemperature, c->GetTemperatureScale()); } auto textWidth = Graphics::TextSize(sampleInfo.Build()).X - 1; g->BlendFilledRect(RectSized(Vec2{ XRES-20-textWidth, 27 }, Vec2{ textWidth+8, 14 }), 0x000000_rgb .WithAlpha(int(alpha*0.5f))); g->BlendText({ XRES-16-textWidth, 30 }, sampleInfo.Build(), 0xFFFFFF_rgb .WithAlpha(int(alpha*0.75f))); } } if(showHud && introText < 51) { //FPS and some version info StringBuilder fpsInfo; fpsInfo << Format::Precision(2) << "FPS: " << ui::Engine::Ref().GetFps(); if (showDebug) { if (ren->findingElement) fpsInfo << " Parts: " << ren->foundElements << "/" << sample.NumParts; else fpsInfo << " Parts: " << sample.NumParts; } if (c->GetReplaceModeFlags()&REPLACE_MODE) fpsInfo << " [REPLACE MODE]"; if (c->GetReplaceModeFlags()&SPECIFIC_DELETE) fpsInfo << " [SPECIFIC DELETE]"; if (ren && ren->GetGridSize()) fpsInfo << " [GRID: " << ren->GetGridSize() << "]"; if (ren && ren->findingElement) fpsInfo << " [FIND]"; int textWidth = Graphics::TextSize(fpsInfo.Build()).X - 1; int alpha = 255-introText*5; g->BlendFilledRect(RectSized(Vec2{ 12, 12 }, Vec2{ textWidth+8, 15 }), 0x000000_rgb .WithAlpha(int(alpha*0.5))); g->BlendText({ 16, 16 }, fpsInfo.Build(), 0x20D8FF_rgb .WithAlpha(int(alpha*0.75))); } //Tooltips if(infoTipPresence) { int infoTipAlpha = (infoTipPresence>50?50:infoTipPresence)*5; g->BlendTextOutline({ (XRES - (Graphics::TextSize(infoTip).X - 1)) / 2, YRES / 2 - 2 }, infoTip, 0xFFFFFF_rgb .WithAlpha(infoTipAlpha)); } if(toolTipPresence && toolTipPosition.X!=-1 && toolTipPosition.Y!=-1 && toolTip.length()) { if (toolTipPosition.Y == Size.Y-MENUSIZE-10) g->BlendTextOutline(toolTipPosition, toolTip, 0xFFFFFF_rgb .WithAlpha(toolTipPresence>51?255:toolTipPresence*5)); else g->BlendText(toolTipPosition, toolTip, 0xFFFFFF_rgb .WithAlpha(toolTipPresence>51?255:toolTipPresence*5)); } if(buttonTipShow > 0) { g->BlendText({ 16, Size.Y-MENUSIZE-24 }, buttonTip, 0xFFFFFF_rgb .WithAlpha(buttonTipShow>51?255:buttonTipShow*5)); } //Introduction text if(introText && showHud) { g->BlendFilledRect(RectSized(Vec2{ 0, 0 }, WINDOW), 0x000000_rgb .WithAlpha(introText>51?102:introText*2)); g->BlendText({ 16, 16 }, introTextMessage, 0xFFFFFF_rgb .WithAlpha(introText>51?255:introText*5)); } } ui::Point GameView::lineSnapCoords(ui::Point point1, ui::Point point2) { ui::Point diff = point2 - point1; if(abs(diff.X / 2) > abs(diff.Y)) // vertical return point1 + ui::Point(diff.X, 0); if(abs(diff.X) < abs(diff.Y / 2)) // horizontal return point1 + ui::Point(0, diff.Y); if(diff.X * diff.Y > 0) // NW-SE return point1 + ui::Point((diff.X + diff.Y)/2, (diff.X + diff.Y)/2); // SW-NE return point1 + ui::Point((diff.X - diff.Y)/2, (diff.Y - diff.X)/2); } ui::Point GameView::rectSnapCoords(ui::Point point1, ui::Point point2) { ui::Point diff = point2 - point1; if(diff.X * diff.Y > 0) // NW-SE return point1 + ui::Point((diff.X + diff.Y)/2, (diff.X + diff.Y)/2); // SW-NE return point1 + ui::Point((diff.X - diff.Y)/2, (diff.Y - diff.X)/2); } std::optional GameView::FindingElementCandidate() const { Tool *active = c->GetActiveTool(0); if (active->Identifier.Contains("_PT_")) { return FindingElement{ Particle::GetProperties()[FIELD_TYPE], active->ToolID }; } else if (active->Identifier == "DEFAULT_UI_PROPERTY") { auto configuration = static_cast(active)->GetConfiguration(); if (configuration) { return FindingElement{ configuration->prop, configuration->propValue }; } } return std::nullopt; }