diff --git a/.gitignore b/.gitignore index 3a16dbbef6..7fcf843fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,7 @@ gitversion eve.db # vscode settings -.vscode \ No newline at end of file +.vscode + +# Local launcher (not for upstream) +run.ps1 \ No newline at end of file diff --git a/gui/app.py b/gui/app.py index ac9ec9ed22..410bd83bbc 100644 --- a/gui/app.py +++ b/gui/app.py @@ -4,7 +4,7 @@ import sys from logbook import Logger pyfalog = Logger(__name__) -from service.settings import LocaleSettings +from service.settings import LocaleSettings, ThemeSettings class PyfaApp(wx.App): @@ -16,6 +16,9 @@ def OnInit(self): # Name for my application. self.appName = "pyfa" + # Windows can only apply appearance before any windows exist. + self.ApplyAppearance() + #------------ # # Simplified init method. @@ -74,4 +77,25 @@ def UpdateLanguage(self, lang=None): else: pyfalog.debug("Cannot find langauge: " + lang) - self.locale = wx.Locale(wx.Locale.FindLanguageInfo(LocaleSettings.defaults['locale']).Language) \ No newline at end of file + self.locale = wx.Locale(wx.Locale.FindLanguageInfo(LocaleSettings.defaults['locale']).Language) + + def ApplyAppearance(self): + try: + appearance_map = { + ThemeSettings.SYSTEM: wx.App.Appearance.System, + ThemeSettings.LIGHT: wx.App.Appearance.Light, + ThemeSettings.DARK: wx.App.Appearance.Dark, + } + except AttributeError: + pyfalog.debug("wx.App.Appearance is not available; skipping theme setup") + return + + mode = ThemeSettings.getInstance().get('appearance') + target = appearance_map.get(mode, wx.App.Appearance.System) + try: + result = self.SetAppearance(target) + pyfalog.info("Set appearance to {0} (result {1})", mode, result) + except (KeyboardInterrupt, SystemExit): + raise + except Exception as e: + pyfalog.warning("Failed to set application appearance: {0}", e) \ No newline at end of file diff --git a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py index 980b0bf655..9bce67d4cc 100644 --- a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py +++ b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py @@ -40,7 +40,7 @@ def populatePanel(self, panel): mainSizer.Add(self.stSetUserPath, 0, wx.ALL, 5) self.inputUserPath = wx.TextCtrl(panel, wx.ID_ANY, config.savePath, wx.DefaultPosition, wx.DefaultSize, 0) self.inputUserPath.SetEditable(False) - self.inputUserPath.SetBackgroundColour((200, 200, 200)) + self.inputUserPath.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer.Add(self.inputUserPath, 0, wx.ALL | wx.EXPAND, 5) # Save DB @@ -50,7 +50,7 @@ def populatePanel(self, panel): self.inputFitDB = wx.TextCtrl(panel, wx.ID_ANY, config.saveDB, wx.DefaultPosition, wx.DefaultSize, 0) self.inputFitDB.SetEditable(False) - self.inputFitDB.SetBackgroundColour((200, 200, 200)) + self.inputFitDB.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer.Add(self.inputFitDB, 0, wx.ALL | wx.EXPAND, 5) # Game Data DB @@ -60,7 +60,7 @@ def populatePanel(self, panel): self.inputGameDB = wx.TextCtrl(panel, wx.ID_ANY, config.gameDB, wx.DefaultPosition, wx.DefaultSize, 0) self.inputGameDB.SetEditable(False) - self.inputGameDB.SetBackgroundColour((200, 200, 200)) + self.inputGameDB.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer.Add(self.inputGameDB, 0, wx.ALL | wx.EXPAND, 5) self.cbsaveInRoot.SetValue(config.saveInRoot) diff --git a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py index 40c17083b2..b92bb0772a 100644 --- a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py +++ b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py @@ -6,7 +6,7 @@ from gui.bitmap_loader import BitmapLoader from gui.preferenceView import PreferenceView from service.fit import Fit -from service.settings import SettingsProvider, LocaleSettings +from service.settings import SettingsProvider, LocaleSettings, ThemeSettings import eos.config import wx.lib.agw.hyperlink as hl @@ -23,6 +23,7 @@ def populatePanel(self, panel): self.openFitsSettings = SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []}) self.localeSettings = LocaleSettings.getInstance() + self.themeSettings = ThemeSettings.getInstance() mainSizer = wx.BoxSizer(wx.VERTICAL) self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0) @@ -95,6 +96,17 @@ def langDisplay(langInfo): wx.DefaultPosition, wx.DefaultSize, 0), 0, wx.LEFT, 15) + self.appearanceChoices = [ThemeSettings.SYSTEM, ThemeSettings.LIGHT, ThemeSettings.DARK] + self.rbAppearance = wx.RadioBox(panel, -1, _t("Appearance (requires restart)"), wx.DefaultPosition, wx.DefaultSize, + [_t("System"), _t("Light"), _t("Dark")], 1, wx.RA_SPECIFY_COLS) + mainSizer.Add(self.rbAppearance, 0, wx.EXPAND | wx.TOP | wx.RIGHT | wx.BOTTOM, 10) + appearance = self.themeSettings.get('appearance') + try: + self.rbAppearance.SetSelection(self.appearanceChoices.index(appearance)) + except ValueError: + self.rbAppearance.SetSelection(0) + self.rbAppearance.Bind(wx.EVT_RADIOBOX, self.OnAppearanceChange) + self.cbGlobalChar = wx.CheckBox(panel, wx.ID_ANY, _t("Use global character"), wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbGlobalChar, 0, wx.ALL | wx.EXPAND, 5) @@ -281,6 +293,10 @@ def onCBExpMutants(self, event): fitID = self.mainFrame.getActiveFit() wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,))) + def OnAppearanceChange(self, event): + self.themeSettings.set('appearance', self.appearanceChoices[event.GetInt()]) + event.Skip() + def OnAddLabelsChange(self, event): self.sFit.serviceFittingOptions["additionsLabels"] = event.GetInt() fitID = self.mainFrame.getActiveFit() diff --git a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py index a89d788cce..55dff81da1 100644 --- a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py +++ b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py @@ -40,7 +40,7 @@ def populatePanel(self, panel): mainSizer.Add(self.stLogPath, 0, wx.ALL, 5) self.inputLogPath = wx.TextCtrl(panel, wx.ID_ANY, config.logPath, wx.DefaultPosition, wx.DefaultSize, 0) self.inputLogPath.SetEditable(False) - self.inputLogPath.SetBackgroundColour((200, 200, 200)) + self.inputLogPath.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer.Add(self.inputLogPath, 0, wx.ALL | wx.EXPAND, 5) import requests @@ -49,7 +49,7 @@ def populatePanel(self, panel): mainSizer.Add(self.certPath, 0, wx.ALL, 5) self.certPathCtrl = wx.TextCtrl(panel, wx.ID_ANY, requests.certs.where(), wx.DefaultPosition, wx.DefaultSize, 0) self.certPathCtrl.SetEditable(False) - self.certPathCtrl.SetBackgroundColour((200, 200, 200)) + self.certPathCtrl.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer.Add(self.certPathCtrl, 0, wx.ALL | wx.EXPAND, 5) # Debug Logging diff --git a/gui/builtinShipBrowser/pfBitmapButton.py b/gui/builtinShipBrowser/pfBitmapButton.py index 647e22debf..a88ff95cc7 100644 --- a/gui/builtinShipBrowser/pfBitmapButton.py +++ b/gui/builtinShipBrowser/pfBitmapButton.py @@ -5,7 +5,7 @@ class PFGenBitmapButton(GenBitmapButton): def __init__(self, parent, id, bitmap, pos, size, style): GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style) - self.bgcolor = wx.Brush(wx.WHITE) + self.bgcolor = wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) def SetBackgroundColour(self, color): self.bgcolor = wx.Brush(color) diff --git a/gui/utils/dark.py b/gui/utils/dark.py index 0d2739f30b..ca0ace86ea 100644 --- a/gui/utils/dark.py +++ b/gui/utils/dark.py @@ -2,8 +2,6 @@ def isDark(): - if 'wxMSW' in wx.PlatformInfo: - return False try: return wx.SystemSettings.GetAppearance().IsDark() except (KeyboardInterrupt, SystemExit): diff --git a/requirements.txt b/requirements.txt index 46ed9241b9..cdac806e2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -wxPython==4.2.1 +wxPython==4.3.0 logbook==1.7.0.post0 numpy==1.26.2 matplotlib==3.8.2 diff --git a/service/settings.py b/service/settings.py index 9d24469033..b02553aa2c 100644 --- a/service/settings.py +++ b/service/settings.py @@ -600,3 +600,36 @@ def set(self, key, value): if key == 'locale' and value not in self.supported_languages(): self.settings[key] = self.DEFAULT self.settings[key] = value + + +class ThemeSettings: + _instance = None + + SYSTEM = "system" + LIGHT = "light" + DARK = "dark" + VALUES = (SYSTEM, LIGHT, DARK) + + defaults = { + 'appearance': SYSTEM, + } + + def __init__(self): + self.settings = SettingsProvider.getInstance().getSettings('pyfaTheme', self.defaults) + + @classmethod + def getInstance(cls): + if cls._instance is None: + cls._instance = ThemeSettings() + return cls._instance + + def get(self, key): + value = self.settings[key] + if key == 'appearance' and value not in self.VALUES: + return self.defaults['appearance'] + return value + + def set(self, key, value): + if key == 'appearance' and value not in self.VALUES: + value = self.defaults['appearance'] + self.settings[key] = value