mirror of
https://github.com/aclist/dztui.git
synced 2026-10-30 14:42:54 +01:00
Merge pull request #466 from aclist/feat/color-aware-app
feat: color scheme aware app
This commit is contained in:
14 files changed
+156
-28
No files matched your search
@@ -51,6 +51,7 @@
|
|||||||
- Update map count (-u flag)
|
- Update map count (-u flag)
|
||||||
- Filter button tooltips
|
- Filter button tooltips
|
||||||
- Expand/collapse changelog versions
|
- Expand/collapse changelog versions
|
||||||
|
- Support local light/dark mode changes on system theme
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Conform to PEP 440 versioning for beta versions
|
- Conform to PEP 440 versioning for beta versions
|
||||||
|
|||||||
+2
-1
@@ -154,4 +154,5 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
use_miles=use_miles,
|
use_miles=use_miles,
|
||||||
)
|
)
|
||||||
print(boot.all_ok)
|
print(boot.all_ok)
|
||||||
App(prefs)
|
app = App(prefs)
|
||||||
|
app.run(None)
|
||||||
@@ -30,6 +30,7 @@ BETA_REPO = "testing"
|
|||||||
APP_NAME = "DZGUI"
|
APP_NAME = "DZGUI"
|
||||||
APP_NAME_LOWER = "dzgui"
|
APP_NAME_LOWER = "dzgui"
|
||||||
APP_NAME_ABBR = "dzg"
|
APP_NAME_ABBR = "dzg"
|
||||||
|
GTK_APP_ID = "io.github.aclist.dzgui"
|
||||||
|
|
||||||
HEX_GREEN = "#32CD32"
|
HEX_GREEN = "#32CD32"
|
||||||
HEX_RED = "#FF0000"
|
HEX_RED = "#FF0000"
|
||||||
@@ -91,3 +92,8 @@ TMP_TARBALL = "/tmp/dzgui.tar.gz"
|
|||||||
TMP_EXE = "/tmp/dzgui/dzgui"
|
TMP_EXE = "/tmp/dzgui/dzgui"
|
||||||
|
|
||||||
DAYZ_COMMUNITY_ROOT = "Missions"
|
DAYZ_COMMUNITY_ROOT = "Missions"
|
||||||
|
|
||||||
|
GIO_SETTINGS_INTERFACE = "org.gnome.desktop.interface"
|
||||||
|
GIO_SETTINGS_PROP = "color-scheme"
|
||||||
|
GIO_SETTINGS_KEY = "gtk-application-prefer-dark-theme"
|
||||||
|
GIO_SETTINGS_VAL = "prefer-dark"
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
- Update map count (-u flag)
|
- Update map count (-u flag)
|
||||||
- Filter button tooltips
|
- Filter button tooltips
|
||||||
- Expand/collapse changelog versions
|
- Expand/collapse changelog versions
|
||||||
|
- Support local light/dark mode changes on system theme
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Conform to PEP 440 versioning for beta versions
|
- Conform to PEP 440 versioning for beta versions
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ from dzgui.views.dialogs.generic import ExceptionDialog
|
|||||||
from dzgui.strings import dialogs
|
from dzgui.strings import dialogs
|
||||||
from dzgui.util._json import read_json, write_json
|
from dzgui.util._json import read_json, write_json
|
||||||
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository.Gtk import main_quit # noqa E402
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -206,7 +201,10 @@ class ConfigManager:
|
|||||||
|
|
||||||
logger.info("Normal user exit")
|
logger.info("Normal user exit")
|
||||||
if window.props.is_maximized:
|
if window.props.is_maximized:
|
||||||
main_quit()
|
app = window.get_application()
|
||||||
|
if app is None:
|
||||||
|
return
|
||||||
|
app.quit()
|
||||||
return
|
return
|
||||||
|
|
||||||
w, h = window.get_size()
|
w, h = window.get_size()
|
||||||
@@ -218,7 +216,10 @@ class ConfigManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
|
||||||
main_quit()
|
app = window.get_application()
|
||||||
|
if app is None:
|
||||||
|
return
|
||||||
|
app.quit()
|
||||||
|
|
||||||
def set_resolution(self, window: "OuterWindow") -> None:
|
def set_resolution(self, window: "OuterWindow") -> None:
|
||||||
if self.prefs.is_game_mode:
|
if self.prefs.is_game_mode:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import gi
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@@ -14,10 +13,6 @@ from dzgui.views.trees.tree_servers import ServerTreeView
|
|||||||
from dzgui.views.trees.tree_server_mods import ServerModTreeView
|
from dzgui.views.trees.tree_server_mods import ServerModTreeView
|
||||||
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
+20
-10
@@ -2,9 +2,14 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal
|
from typing import TYPE_CHECKING, Literal, Self
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, STEAM_ICON
|
from dzgui.const.constants import (
|
||||||
|
APP_NAME,
|
||||||
|
APP_NAME_LOWER,
|
||||||
|
GTK_APP_ID,
|
||||||
|
STEAM_ICON,
|
||||||
|
)
|
||||||
from dzgui.const.enum import NotebookPage
|
from dzgui.const.enum import NotebookPage
|
||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -14,6 +19,7 @@ from dzgui.views.components.connect_panel import ConnectPanel
|
|||||||
from dzgui.views.components.crumbs import Breadcrumbs
|
from dzgui.views.components.crumbs import Breadcrumbs
|
||||||
from dzgui.views.components.right_panel import RightPanel
|
from dzgui.views.components.right_panel import RightPanel
|
||||||
from dzgui.views.components.statusbar import Statusbar
|
from dzgui.views.components.statusbar import Statusbar
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
# TODO: import notebook only and add components there?
|
# TODO: import notebook only and add components there?
|
||||||
@@ -46,9 +52,11 @@ logger = logging.getLogger(APP_NAME)
|
|||||||
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
||||||
|
|
||||||
|
|
||||||
class OuterWindow(Gtk.Window):
|
class OuterWindow(Gtk.ApplicationWindow):
|
||||||
def __init__(self) -> None:
|
def __init__(self, app: Gtk.Application) -> None:
|
||||||
super().__init__(title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER)
|
super().__init__(
|
||||||
|
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
||||||
|
)
|
||||||
|
|
||||||
self.hb = AppHeaderBar()
|
self.hb = AppHeaderBar()
|
||||||
MainController.register_widget("window", self)
|
MainController.register_widget("window", self)
|
||||||
@@ -279,13 +287,18 @@ class Grid(Gtk.Grid):
|
|||||||
self.right_panel.refresh_button.set_visible(state)
|
self.right_panel.refresh_button.set_visible(state)
|
||||||
|
|
||||||
|
|
||||||
class App(Gtk.Application):
|
class App(ColorAwareApp, Gtk.Application): # type: ignore
|
||||||
def __init__(self, prefs: "UserPrefs") -> None:
|
def __init__(self, prefs: "UserPrefs") -> None:
|
||||||
|
super().__init__(application_id=GTK_APP_ID)
|
||||||
|
|
||||||
GLib.set_prgname(APP_NAME)
|
GLib.set_prgname(APP_NAME)
|
||||||
MainController.set_prefs(prefs)
|
MainController.set_prefs(prefs)
|
||||||
|
|
||||||
self.win = OuterWindow()
|
self._setup_signals()
|
||||||
|
self.connect("activate", self._on_activate)
|
||||||
|
|
||||||
|
def _on_activate(self, app: Self) -> None:
|
||||||
|
self.win = OuterWindow(app)
|
||||||
|
|
||||||
accel = Gtk.AccelGroup()
|
accel = Gtk.AccelGroup()
|
||||||
accel.connect(
|
accel.connect(
|
||||||
@@ -296,9 +309,6 @@ class App(Gtk.Application):
|
|||||||
)
|
)
|
||||||
self.win.add_accel_group(accel)
|
self.win.add_accel_group(accel)
|
||||||
|
|
||||||
self._setup_signals()
|
|
||||||
Gtk.main()
|
|
||||||
|
|
||||||
def _setup_signals(self) -> None:
|
def _setup_signals(self) -> None:
|
||||||
SIGNAL_ADD = "signal_add"
|
SIGNAL_ADD = "signal_add"
|
||||||
SIGNAL_ADD_FULL = "signal_add_full"
|
SIGNAL_ADD_FULL = "signal_add_full"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ from typing import Any, Literal, Self, TYPE_CHECKING
|
|||||||
# import time
|
# import time
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
# TODO: import dialog titles
|
# TODO: import dialog titles
|
||||||
from dzgui.api.mods import remove_stale_signatures as remove_stale
|
from dzgui.api.mods import remove_stale_signatures as remove_stale
|
||||||
from dzgui.config.ipdb import get_ipdb
|
from dzgui.config.ipdb import get_ipdb
|
||||||
@@ -37,7 +40,7 @@ class Success(Enum):
|
|||||||
|
|
||||||
# TODO: strings for "Running", "Failed", etc.
|
# TODO: strings for "Running", "Failed", etc.
|
||||||
# TODO: add margins to tree
|
# TODO: add margins to tree
|
||||||
class BootDialog(Gtk.Dialog):
|
class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
|
||||||
def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None:
|
def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
title=dialog_header,
|
title=dialog_header,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import sys
|
|||||||
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
from dzgui.util.strings import dialog_error, dialog_header
|
from dzgui.util.strings import dialog_error, dialog_header
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ gi.require_version("Gtk", "3.0")
|
|||||||
from gi.repository import Gtk # noqa E402
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
class AbortDialog(Gtk.MessageDialog):
|
class AbortDialog(ColorAwareApp, Gtk.MessageDialog): # type: ignore
|
||||||
def __init__(self, string: str, buttons: Gtk.ButtonsType) -> None:
|
def __init__(self, string: str, buttons: Gtk.ButtonsType) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
title=dialog_header,
|
title=dialog_header,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from dzgui.util.css import load_css
|
|||||||
from dzgui.util.format import format_exception
|
from dzgui.util.format import format_exception
|
||||||
from dzgui.views.components.box import HBox
|
from dzgui.views.components.box import HBox
|
||||||
from dzgui.views.dialogs.wizard import ScrolledWizardPage, CheckboxWithLabel
|
from dzgui.views.dialogs.wizard import ScrolledWizardPage, CheckboxWithLabel
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -217,7 +218,7 @@ class UninstallPage(ScrolledWizardPage):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UninstallWizard(Gtk.Application):
|
class UninstallWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||||
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
|
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
config = Path(paths["XDG_CONFIG_HOME"])
|
config = Path(paths["XDG_CONFIG_HOME"])
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from dzgui.util.css import add_class, load_css
|
|||||||
from dzgui.views.components.buttons import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
from dzgui.views.components.entry import APIEntry
|
from dzgui.views.components.entry import APIEntry
|
||||||
from dzgui.views.components.misc import ClientCombo
|
from dzgui.views.components.misc import ClientCombo
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -706,7 +707,7 @@ class SteamPathPage(EnumeratedWizardPage):
|
|||||||
self.err_box.set_visible(False)
|
self.err_box.set_visible(False)
|
||||||
|
|
||||||
|
|
||||||
class SetupWizard(Gtk.Application):
|
class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
|
def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
GLib.set_prgname(APP_NAME)
|
GLib.set_prgname(APP_NAME)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from dzgui.const.constants import (
|
||||||
|
GIO_SETTINGS_KEY,
|
||||||
|
GIO_SETTINGS_INTERFACE,
|
||||||
|
GIO_SETTINGS_PROP,
|
||||||
|
GIO_SETTINGS_VAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gio # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class ColorAwareApp:
|
||||||
|
"""
|
||||||
|
Polls the local theme's color scheme settings.
|
||||||
|
Intended to be consumed by Gtk.Applications (or ephemeral widgets like EarlyAlertDialogs)
|
||||||
|
on initialization, prior to being realized. Applying scheme changes too late in the chain
|
||||||
|
may cause a blinking effect.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.interface_settings = Gio.Settings.new(GIO_SETTINGS_INTERFACE)
|
||||||
|
self.interface_settings.connect(
|
||||||
|
"changed::color-scheme", self._on_color_scheme_changed
|
||||||
|
)
|
||||||
|
self.default_settings = Gtk.Settings.get_default()
|
||||||
|
self._on_color_scheme_changed(self.interface_settings, GIO_SETTINGS_PROP)
|
||||||
|
|
||||||
|
def _on_color_scheme_changed(self, settings: Gio.Settings, prop: str) -> None:
|
||||||
|
state = self.interface_settings.get_string(prop) == GIO_SETTINGS_VAL
|
||||||
|
if self.default_settings is None:
|
||||||
|
return
|
||||||
|
self.default_settings.set_property(GIO_SETTINGS_KEY, state)
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def iterate(search_str: str) -> None:
|
def iterate(search_str: str) -> None:
|
||||||
# TODO: exclude files from .gitignore
|
# TODO: exclude files from .gitignore
|
||||||
print()
|
print()
|
||||||
print(f"Searching for '{search_str}'")
|
print(f"Searching for '{search_str}'")
|
||||||
print()
|
print()
|
||||||
files = Path(".").rglob("*")
|
files = Path(".").rglob("*")
|
||||||
ignore = [".git", ".mypy", "build/", "test.py"]
|
ignore = [
|
||||||
|
"scripts/",
|
||||||
|
".git",
|
||||||
|
"egg",
|
||||||
|
"CHANGELOG",
|
||||||
|
".mypy",
|
||||||
|
"build/",
|
||||||
|
"test.py",
|
||||||
|
"bugs",
|
||||||
|
"pyproject.toml",
|
||||||
|
]
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.is_dir():
|
if file.is_dir():
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import pytest
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from dzgui.const.constants import GIO_SETTINGS_KEY
|
||||||
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gio, GLib # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class TestWidget(ColorAwareApp, Gtk.Button):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.state: bool
|
||||||
|
|
||||||
|
def check_theme(self) -> None:
|
||||||
|
self.state = self.get_settings().get_property(GIO_SETTINGS_KEY)
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def widget() -> TestWidget:
|
||||||
|
return TestWidget()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)])
|
||||||
|
def test_light_mode(widget, key: str, expect: bool) -> None:
|
||||||
|
local_pref = subprocess.run(
|
||||||
|
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"gsettings",
|
||||||
|
"set",
|
||||||
|
"org.gnome.desktop.interface",
|
||||||
|
"color-scheme",
|
||||||
|
key,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# NOTE: allow changes to propagate
|
||||||
|
GLib.idle_add(widget.check_theme)
|
||||||
|
Gtk.main()
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"gsettings",
|
||||||
|
"set",
|
||||||
|
"org.gnome.desktop.interface",
|
||||||
|
"color-scheme",
|
||||||
|
local_pref,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert widget.state is expect
|
||||||
Reference in new issue
Block a user