diff --git a/src/Format.cpp b/src/Format.cpp index 78372bc8f..286acf51e 100644 --- a/src/Format.cpp +++ b/src/Format.cpp @@ -178,3 +178,57 @@ ByteString format::URLDecode(ByteString source) } return result; } + +void format::RenderTemperature(StringBuilder &sb, float temp, int scale) +{ + switch (scale) + { + case 1: + sb << (temp - 273.15f) << " °C"; + break; + case 2: + sb << (temp - 273.15f) * 1.8f + 32.0f << " °F"; + break; + default: + sb << temp << " K"; + break; + } +} + +float format::StringToTemperature(String str, int defaultScale) +{ + auto scale = defaultScale; + if (str.size()) + { + if (str.EndsWith("K")) + { + scale = 0; + str = str.SubstrFromEnd(1); + } + else if (str.EndsWith("C")) + { + scale = 1; + str = str.SubstrFromEnd(1); + } + else if (str.EndsWith("F")) + { + scale = 2; + str = str.SubstrFromEnd(1); + } + } + if (!str.size()) + { + throw std::out_of_range("empty string"); + } + auto out = str.ToNumber<float>(); + switch (scale) + { + case 1: + out = out + 273.15; + break; + case 2: + out = (out - 32.0f) / 1.8f + 273.15f; + break; + } + return out; +} diff --git a/src/Format.h b/src/Format.h index 2c28da979..e395a673c 100644 --- a/src/Format.h +++ b/src/Format.h @@ -14,4 +14,6 @@ namespace format ByteString UnixtimeToDateMini(time_t unixtime); String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false); std::vector<char> VideoBufferToPPM(const VideoBuffer & vidBuf); + void RenderTemperature(StringBuilder &sb, float temp, int scale); + float StringToTemperature(String str, int defaultScale); } diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index fd942b1e8..b9d53ddb7 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -1035,6 +1035,16 @@ bool GameController::GetDebugHUD() return gameView->GetDebugHUD(); } +void GameController::SetTemperatureScale(int temperatureScale) +{ + gameModel->SetTemperatureScale(temperatureScale); +} + +int GameController::GetTemperatureScale() +{ + return gameModel->GetTemperatureScale(); +} + void GameController::SetActiveColourPreset(int preset) { gameModel->SetActiveColourPreset(preset); diff --git a/src/gui/game/GameController.h b/src/gui/game/GameController.h index ea61cfd4f..a3364f32e 100644 --- a/src/gui/game/GameController.h +++ b/src/gui/game/GameController.h @@ -119,6 +119,8 @@ public: bool GetBrushEnable(); void SetDebugHUD(bool hudState); bool GetDebugHUD(); + void SetTemperatureScale(int temperatureScale); + int GetTemperatureScale(); void SetDebugFlags(unsigned int flags) { debugFlags = flags; } void SetActiveMenu(int menuID); std::vector<Menu*> GetMenuList(); diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp index 0fc6cfc8b..b5d112c91 100644 --- a/src/gui/game/GameModel.cpp +++ b/src/gui/game/GameModel.cpp @@ -154,6 +154,7 @@ GameModel::GameModel(): mouseClickRequired = Client::Ref().GetPrefBool("MouseClickRequired", false); includePressure = Client::Ref().GetPrefBool("Simulation.IncludePressure", true); + temperatureScale = Client::Ref().GetPrefInteger("Renderer.TemperatureScale", 1); ClearSimulation(); } @@ -534,6 +535,11 @@ int GameModel::GetEdgeMode() return this->edgeMode; } +void GameModel::SetTemperatureScale(int temperatureScale) +{ + this->temperatureScale = temperatureScale; +} + void GameModel::SetAmbientAirTemperature(float ambientAirTemp) { this->ambientAirTemp = ambientAirTemp; diff --git a/src/gui/game/GameModel.h b/src/gui/game/GameModel.h index 142e54a15..d3e00e6ba 100644 --- a/src/gui/game/GameModel.h +++ b/src/gui/game/GameModel.h @@ -81,6 +81,7 @@ private: bool mouseClickRequired; bool includePressure; bool perfectCircle = true; + int temperatureScale; size_t activeColourPreset; std::vector<ui::Colour> colourPresets; @@ -123,6 +124,11 @@ public: void SetEdgeMode(int edgeMode); int GetEdgeMode(); + void SetTemperatureScale(int temperatureScale); + inline int GetTemperatureScale() const + { + return temperatureScale; + } void SetAmbientAirTemperature(float ambientAirTemp); float GetAmbientAirTemperature(); void SetDecoSpace(int decoSpace); diff --git a/src/gui/game/GameView.cpp b/src/gui/game/GameView.cpp index 0808050e5..bd1bbc4f0 100644 --- a/src/gui/game/GameView.cpp +++ b/src/gui/game/GameView.cpp @@ -2285,7 +2285,8 @@ void GameView::OnDraw() else if (ctype) sampleInfo << " (" << ctype << ")"; } - sampleInfo << ", Temp: " << (sample.particle.temp - 273.15f) << " C"; + 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) { @@ -2315,7 +2316,8 @@ void GameView::OnDraw() else { sampleInfo << c->BasicParticleInfo(sample.particle); - sampleInfo << ", Temp: " << sample.particle.temp - 273.15f << " C"; + sampleInfo << ", Temp: "; + format::RenderTemperature(sampleInfo, sample.particle.temp, c->GetTemperatureScale()); sampleInfo << ", Pressure: " << sample.AirPressure; } } @@ -2386,7 +2388,10 @@ void GameView::OnDraw() sampleInfo << ", GX: " << sample.GravityVelocityX << " GY: " << sample.GravityVelocityY; if (c->GetAHeatEnable()) - sampleInfo << ", AHeat: " << sample.AirTemperature - 273.15f << " C"; + { + sampleInfo << ", AHeat: "; + format::RenderTemperature(sampleInfo, sample.AirTemperature, c->GetTemperatureScale()); + } textWidth = Graphics::textwidth(sampleInfo.Build()); g->fillrect(XRES-20-textWidth, 27, textWidth+8, 14, 0, 0, 0, int(alpha*0.5f)); diff --git a/src/gui/game/PropertyTool.cpp b/src/gui/game/PropertyTool.cpp index 5c56e3e4a..937360b3c 100644 --- a/src/gui/game/PropertyTool.cpp +++ b/src/gui/game/PropertyTool.cpp @@ -2,6 +2,7 @@ #include "client/Client.h" #include "Menu.h" +#include "Format.h" #include "gui/game/GameModel.h" #include "gui/Style.h" @@ -23,31 +24,6 @@ #include <iostream> -void ParseFloatProperty(String value, float &out) -{ - if (!value.size()) - { - throw std::out_of_range("empty string"); - } - if (value.EndsWith("C")) - { - float v = value.SubstrFromEnd(1).ToNumber<float>(); - out = v + 273.15; - } - else if(value.EndsWith("F")) - { - float v = value.SubstrFromEnd(1).ToNumber<float>(); - out = (v-32.0f)*5/9+273.15f; - } - else - { - out = value.ToNumber<float>(); - } -#ifdef DEBUG - std::cout << "Got float value " << out << std::endl; -#endif -} - class PropertyWindow: public ui::Window { public: @@ -219,7 +195,7 @@ void PropertyWindow::SetProperty(bool warn) } case StructProperty::Float: { - ParseFloatProperty(value, tool->propValue.Float); + tool->propValue.Float = format::StringToTemperature(value, tool->gameModel->GetTemperatureScale()); } break; default: diff --git a/src/gui/options/OptionsController.cpp b/src/gui/options/OptionsController.cpp index cb7cd23b8..15393233a 100644 --- a/src/gui/options/OptionsController.cpp +++ b/src/gui/options/OptionsController.cpp @@ -67,6 +67,11 @@ void OptionsController::SetEdgeMode(int edgeMode) model->SetEdgeMode(edgeMode); } +void OptionsController::SetTemperatureScale(int temperatureScale) +{ + model->SetTemperatureScale(temperatureScale); +} + void OptionsController::SetFullscreen(bool fullscreen) { model->SetFullscreen(fullscreen); diff --git a/src/gui/options/OptionsController.h b/src/gui/options/OptionsController.h index b50a9fbb6..b7f98dd3d 100644 --- a/src/gui/options/OptionsController.h +++ b/src/gui/options/OptionsController.h @@ -26,6 +26,7 @@ public: void SetAirMode(int airMode); void SetAmbientAirTemperature(float ambientAirTemp); void SetEdgeMode(int edgeMode); + void SetTemperatureScale(int temperatureScale); void SetFullscreen(bool fullscreen); void SetAltFullscreen(bool altFullscreen); void SetForceIntegerScaling(bool forceIntegerScaling); diff --git a/src/gui/options/OptionsModel.cpp b/src/gui/options/OptionsModel.cpp index d3b7242fc..5005873f3 100644 --- a/src/gui/options/OptionsModel.cpp +++ b/src/gui/options/OptionsModel.cpp @@ -90,6 +90,17 @@ void OptionsModel::SetEdgeMode(int edgeMode) notifySettingsChanged(); } +int OptionsModel::GetTemperatureScale() +{ + return gModel->GetTemperatureScale(); +} +void OptionsModel::SetTemperatureScale(int temperatureScale) +{ + Client::Ref().SetPref("Renderer.TemperatureScale", temperatureScale); + gModel->SetTemperatureScale(temperatureScale); + notifySettingsChanged(); +} + float OptionsModel::GetAmbientAirTemperature() { return gModel->GetSimulation()->air->ambientAirTemp; diff --git a/src/gui/options/OptionsModel.h b/src/gui/options/OptionsModel.h index 2ac084487..105f03640 100644 --- a/src/gui/options/OptionsModel.h +++ b/src/gui/options/OptionsModel.h @@ -32,6 +32,8 @@ public: void SetAmbientAirTemperature(float ambientAirTemp); int GetEdgeMode(); void SetEdgeMode(int edgeMode); + int GetTemperatureScale(); + void SetTemperatureScale(int temperatureScale); int GetGravityMode(); void SetGravityMode(int gravityMode); float GetCustomGravityX(); diff --git a/src/gui/options/OptionsView.cpp b/src/gui/options/OptionsView.cpp index b75837ed1..bb7608978 100644 --- a/src/gui/options/OptionsView.cpp +++ b/src/gui/options/OptionsView.cpp @@ -4,6 +4,7 @@ #include <cstring> #include <cmath> #include "SDLCompat.h" +#include "Format.h" #include "OptionsController.h" #include "OptionsModel.h" @@ -235,6 +236,19 @@ OptionsView::OptionsView(): tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; scrollPanel->AddChild(tempLabel); + currentY+=20; + temperatureScale = new ui::DropDown(ui::Point(Size.X-95, currentY), ui::Point(80, 16)); + scrollPanel->AddChild(temperatureScale); + temperatureScale->AddOption(std::pair<String, int>("Kelvin", 0)); + temperatureScale->AddOption(std::pair<String, int>("Celsius", 1)); + temperatureScale->AddOption(std::pair<String, int>("Fahrenheit", 2)); + temperatureScale->SetActionCallback({ [this] { c->SetTemperatureScale(temperatureScale->GetOption().second); } }); + + tempLabel = new ui::Label(ui::Point(8, currentY), ui::Point(Size.X-96, 16), "Temperature Scale"); + tempLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; + tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + scrollPanel->AddChild(tempLabel); + currentY+=20; tmpSeparator = new Separator(ui::Point(0, currentY), ui::Point(Size.X, 1)); scrollPanel->AddChild(tmpSeparator); @@ -437,6 +451,25 @@ void OptionsView::UpdateAmbientAirTempPreview(float airTemp, bool isValid) ambientAirTempPreview->Appearance.BackgroundHover = ambientAirTempPreview->Appearance.BackgroundInactive; } +void OptionsView::AmbientAirTempToTextBox(float airTemp) +{ + StringBuilder sb; + sb << Format::Precision(2); + switch (temperatureScale->GetOption().second) + { + case 1: + sb << (airTemp - 273.15f) << "C"; + break; + case 2: + sb << (airTemp - 273.15f) * 1.8f + 32.0f << "F"; + break; + default: + sb << airTemp; + break; + } + ambientAirTemp->SetText(sb.Build()); +} + void OptionsView::UpdateAirTemp(String temp, bool isDefocus) { // Parse air temp and determine validity @@ -444,8 +477,7 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus) bool isValid; try { - void ParseFloatProperty(String value, float &out); - ParseFloatProperty(temp, airTemp); + airTemp = format::StringToTemperature(temp, temperatureScale->GetOption().second); isValid = true; } catch (const std::exception &ex) @@ -470,10 +502,7 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus) else return; - // Update textbox with the new value - StringBuilder sb; - sb << Format::Precision(2) << airTemp; - ambientAirTemp->SetText(sb.Build()); + AmbientAirTempToTextBox(airTemp); } // Out of range temperatures are invalid, preview should go away else if (isValid && (airTemp < MIN_TEMP || airTemp > MAX_TEMP)) @@ -488,20 +517,18 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus) void OptionsView::NotifySettingsChanged(OptionsModel * sender) { + temperatureScale->SetOption(sender->GetTemperatureScale()); // has to happen before AmbientAirTempToTextBox is called heatSimulation->SetChecked(sender->GetHeatSimulation()); ambientHeatSimulation->SetChecked(sender->GetAmbientHeatSimulation()); newtonianGravity->SetChecked(sender->GetNewtonianGravity()); waterEqualisation->SetChecked(sender->GetWaterEqualisation()); airMode->SetOption(sender->GetAirMode()); // Initialize air temp and preview only when the options menu is opened, and not when user is actively editing the textbox - if (!initializedAirTempPreview) + if (!ambientAirTemp->IsFocused()) { - initializedAirTempPreview = true; float airTemp = sender->GetAmbientAirTemperature(); UpdateAmbientAirTempPreview(airTemp, true); - StringBuilder sb; - sb << Format::Precision(2) << airTemp; - ambientAirTemp->SetText(sb.Build()); + AmbientAirTempToTextBox(airTemp); } gravityMode->SetOption(sender->GetGravityMode()); customGravityX = sender->GetCustomGravityX(); diff --git a/src/gui/options/OptionsView.h b/src/gui/options/OptionsView.h index 20431d3f9..5b519c4f2 100644 --- a/src/gui/options/OptionsView.h +++ b/src/gui/options/OptionsView.h @@ -27,6 +27,7 @@ class OptionsView: public ui::Window ui::Button * ambientAirTempPreview; ui::DropDown * gravityMode; ui::DropDown * edgeMode; + ui::DropDown * temperatureScale; ui::DropDown * scale; ui::Checkbox * resizable; ui::Checkbox * fullscreen; @@ -41,8 +42,8 @@ class OptionsView: public ui::Window ui::Checkbox * perfectCirclePressure; ui::ScrollPanel * scrollPanel; float customGravityX, customGravityY; - bool initializedAirTempPreview = false; void UpdateAmbientAirTempPreview(float airTemp, bool isValid); + void AmbientAirTempToTextBox(float airTemp); void UpdateAirTemp(String temp, bool isDefocus); public: OptionsView(); diff --git a/src/lua/TPTScriptInterface.cpp b/src/lua/TPTScriptInterface.cpp index e5c110108..2fdd2f1c7 100644 --- a/src/lua/TPTScriptInterface.cpp +++ b/src/lua/TPTScriptInterface.cpp @@ -8,6 +8,7 @@ #include <cmath> #include "Config.h" +#include "Format.h" #include "simulation/Simulation.h" #include "simulation/Air.h" @@ -271,6 +272,21 @@ AnyType TPTScriptInterface::tptS_set(std::deque<String> * words) //Selector int newValue = 0; float newValuef = 0.0f; + if (property.Value() == "temp") + { + // convert non-string temperature values to strings to format::StringToTemperature can take care of them + switch (value.GetType()) + { + case TypeNumber: + value = StringType(String::Build(((NumberType)value).Value())); + break; + case TypeFloat: + value = StringType(String::Build(((FloatType)value).Value())); + break; + default: + break; + } + } if (value.GetType() == TypeNumber) { newValuef = float(newValue = ((NumberType)value).Value()); @@ -283,13 +299,14 @@ AnyType TPTScriptInterface::tptS_set(std::deque<String> * words) { if (property.Value() == "temp") { - String newString = ((StringType)value).Value(); - if (newString.at(newString.length()-1) == 'C') - newValuef = atof(newString.SubstrFromEnd(1).ToUtf8().c_str())+273.15; - else if (newString.at(newString.length()-1) == 'F') - newValuef = (atof(newString.SubstrFromEnd(1).ToUtf8().c_str())-32.0f)*5/9+273.15f; - else + try + { + newValuef = format::StringToTemperature(((StringType)value).Value(), c->GetTemperatureScale()); + } + catch (const std::exception &ex) + { throw GeneralException("Invalid value for assignment"); + } } else {