Broke up UI into seperate file, reorganisation of dirtree, added ViewStack

This commit is contained in:
2025-12-02 23:04:11 +01:00
parent cc1401ff96
commit f8a10249bb
41 changed files with 2742 additions and 3564 deletions

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/gay/valhrafnaz/Gnomeframe/">
<file>style.css</file>
<file preprocess="xml-stripblanks">ui/window.ui</file>
<file preprocess="xml-stripblanks">ui/shortcuts-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/checklist.ui</file>
<file preprocess="xml-stripblanks">icons/bw/scalable/floppy-symbolic.svg</file>
</gresource>
</gresources>

View File

View File

@@ -0,0 +1,46 @@
#!@PYTHON@
# gnomeframe.in
#
# Copyright 2025 nihil
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import os
import sys
import signal
import locale
import gettext
VERSION = '@VERSION@'
pkgdatadir = '@pkgdatadir@'
localedir = '@localedir@'
sys.path.insert(1, pkgdatadir)
signal.signal(signal.SIGINT, signal.SIG_DFL)
locale.bindtextdomain('gnomeframe', localedir)
locale.textdomain('gnomeframe')
gettext.install('gnomeframe', localedir)
if __name__ == '__main__':
import gi
from gi.repository import Gio
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'gnomeframe.gresource'))
resource._register()
from gnomeframe import main
sys.exit(main.main(VERSION))

View File

@@ -0,0 +1,2 @@
# gnomeframe/pages/__init__.py
"""Subpackage that holds the individual ViewStack pages."""

View File

@@ -1,29 +1,13 @@
# window.py
#
# Copyright 2025 nihil
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import json
import os
import math
from pathlib import Path
from gi.repository import Gtk, Adw, GObject, GLib
TOTAL_WARFRAMES=62 # pre uriel, valid until 10-12-2025
TOTAL_PRIMES=49 # post gyre prime, valid until ca. may-2026
from gi.repository import Adw
from gi.repository import Gtk, Gio
from gi.repository import GLib
frame_button_ids = [
"ash",
@@ -151,45 +135,11 @@ frame_button_ids = [
]
def get_config_dir(app_id: str) -> Path:
base = Path(GLib.get_user_config_dir())
cfg_dir = base / app_id
cfg_dir.mkdir(parents=True, exist_ok=True)
print(f"Found config dir at: {cfg_dir}")
return cfg_dir
APP_ID = "gay.valhrafnaz.Gnomeframe"
CONFIG_DIR = get_config_dir(APP_ID)
CONFIG_FILE = CONFIG_DIR / "profile.json"
TOTAL_WARFRAMES=62 # pre uriel, valid until 10-12-2025
TOTAL_PRIMES=49 # post gyre prime, valid until ca. may-2026
def load_profile() -> dict[str, bool]:
try:
with CONFIG_FILE.open("r", encoding="utf-8") as f:
data = json.load(f)
return {k: bool(v) for k, v in data.items()}
except FileNotFoundError:
return {}
except (json.JSONDecodeError, OSError) as exc:
print(f"Could not load profile: {exc}")
return {}
def save_profile(state: dict[str, bool]) -> None:
try:
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
with CONFIG_FILE.open("w", encoding="utf-8") as f:
json.dump(state, f, indent=2, sort_keys=True)
except OSError as exc:
print(f"Failed to write profile: {exc}")
@Gtk.Template(resource_path='/gay/valhrafnaz/Gnomeframe/ui/compiled/window.ui')
class GnomeframeWindow(Adw.ApplicationWindow):
__gtype_name__ = 'GnomeframeWindow'
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/checklist.ui")
class ChecklistPage(Gtk.Box):
__gtype_name__ = "ChecklistPage"
# tooling for checklist
frame_box = Gtk.Template.Child()
btns_basic = Gtk.Template.Child()
btns_prime = Gtk.Template.Child()
@@ -197,41 +147,30 @@ class GnomeframeWindow(Adw.ApplicationWindow):
basic_percent = Gtk.Template.Child()
prime_counter = Gtk.Template.Child()
prime_percent = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._profile: dict[str, bool] = load_profile()
def __init__(self, *, parent: Adw.ViewStack, window):
super().__init__()
self._parent_window = window
parent.add_titled(self, "checklist", "Checklist")
wrapper = parent.get_page(self)
wrapper.set_icon_name("check-round-outline2-symbolic")
self._connect_frame_btns()
reset_action = Gio.SimpleAction.new("reset-selections", None)
reset_action.connect("activate", self._reset_all)
self.get_application().add_action(reset_action)
self.connect("close-request", self._on_close_request)
self._calc_frames()
# self.settings = Gio.Settings(schema_id="gay.valhrafnaz.Gnomeframe")
# self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT)
# self.settings.bind("window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT)
# self.settings.bind("window-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
def _connect_frame_btns(self):
button = self.btns_basic.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
btn_id = button.get_name()
button.set_active(self._profile.get(btn_id, False))
button.set_active(self._parent_window._profile.get(btn_id, False))
button.connect("toggled", self._on_button_toggled)
button = button.get_next_sibling()
button = self.btns_prime.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
btn_id = button.get_name()
button.set_active(self._profile.get(btn_id, False))
button.set_active(self._parent_window._profile.get(btn_id, False))
button.connect("toggled", self._on_button_toggled)
button = button.get_next_sibling()
@@ -271,25 +210,7 @@ class GnomeframeWindow(Adw.ApplicationWindow):
print("what")
return
self._profile[btn_id] = btn.get_active()
save_profile(self._profile)
super.save_profile(self._profile)
self._calc_frames()
def _reset_all(self, action, param):
self._profile.clear()
button = self.btns_basic.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
button.set_active(False)
button = button.get_next_sibling()
button = self.btns_prime.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
button.set_active(False)
button = button.get_next_sibling()
save_profile(self._profile)
def _on_close_request(self, *args):
save_profile(self._profile)
return False

View File

@@ -0,0 +1,16 @@
from gi.repository import Gtk, Adw, GObject, GLib
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/home.ui")
class HomePage(Gtk.Box):
__gtype_name__ = "HomePage"
def __init__(self, *, parent: Adw.ViewStack):
"""
`parent` is the ViewStack that will host this page.
By passing it to the superclass constructor we tell GTK to
insert the newly created page into that stack automatically.
"""
super().__init__()
parent.add_titled(self, "home", "Home")
wrapper = parent.get_page(self)
wrapper.set_icon_name("compass2-symbolic")

View File

@@ -0,0 +1,11 @@
from gi.repository import Gtk, Adw, GObject, GLib
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/settings.ui")
class SettingsPage(Gtk.Box):
__gtype_name__ = "SettingsPage"
def __init__(self, *, parent: Adw.ViewStack):
super().__init__()
parent.add_titled(self, "settings", "Settings")
wrapper = parent.get_page(self)
wrapper.set_icon_name("settings-symbolic")

112
src/gnomeframe/window.py Normal file
View File

@@ -0,0 +1,112 @@
# window.py
#
# Copyright 2025 nihil
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import json
import os
from pathlib import Path
from .pages.home import HomePage
from .pages.settings import SettingsPage
from .pages.checklist import ChecklistPage
from gi.repository import Adw
from gi.repository import Gtk, Gio
from gi.repository import GLib
def get_config_dir(app_id: str) -> Path:
base = Path(GLib.get_user_config_dir())
cfg_dir = base / app_id
cfg_dir.mkdir(parents=True, exist_ok=True)
print(f"Found config dir at: {cfg_dir}")
return cfg_dir
APP_ID = "gay.valhrafnaz.Gnomeframe"
CONFIG_DIR = get_config_dir(APP_ID)
CONFIG_FILE = CONFIG_DIR / "profile.json"
def load_profile() -> dict[str, bool]:
try:
with CONFIG_FILE.open("r", encoding="utf-8") as f:
data = json.load(f)
return {k: bool(v) for k, v in data.items()}
except FileNotFoundError:
return {}
except (json.JSONDecodeError, OSError) as exc:
print(f"Could not load profile: {exc}")
return {}
def save_profile(state: dict[str, bool]) -> None:
try:
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
with CONFIG_FILE.open("w", encoding="utf-8") as f:
json.dump(state, f, indent=2, sort_keys=True)
except OSError as exc:
print(f"Failed to write profile: {exc}")
@Gtk.Template(resource_path='/gay/valhrafnaz/Gnomeframe/ui/window.ui')
class GnomeframeWindow(Adw.ApplicationWindow):
__gtype_name__ = 'GnomeframeWindow'
viewstack = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._profile: dict[str, bool] = load_profile()
self._load_page_templates()
reset_action = Gio.SimpleAction.new("reset-selections", None)
reset_action.connect("activate", self._reset_all)
self.get_application().add_action(reset_action)
self.connect("close-request", self._on_close_request)
# self.settings = Gio.Settings(schema_id="gay.valhrafnaz.Gnomeframe")
# self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT)
# self.settings.bind("window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT)
# self.settings.bind("window-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
def _load_page_templates(self):
self.home_page = HomePage(parent=self.viewstack)
self.settings_page = SettingsPage(parent=self.viewstack)
self.checklist_page = ChecklistPage(parent=self.viewstack, window=self)
def _reset_all(self, action, param):
self._profile.clear()
button = self.checklist_page.btns_basic.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
button.set_active(False)
button = button.get_next_sibling()
button = self.checklist_page.btns_prime.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
button.set_active(False)
button = button.get_next_sibling()
save_profile(self._profile)
def _on_close_request(self, *args):
save_profile(self._profile)
return False

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3.96875 1 s -2.96875 0 -2.96875 2.96875 v 8.03125 s 0 0.5 0.3125 0.71875 l 1.6875 1.6875 v -10.40625 c 0 -1 1 -1 1 -1 h 8 s 1 0 1 1 v 8 c 0 1 -1 1 -1 1 h -1 v 2 h 1 s 3 0 3 -2.96875 v -8.0625 s 0 -2.96875 -2.96875 -2.96875 z m 1.03125 8 c -0.554688 0 -1 0.445312 -1 1 v 4 c 0 0.554688 0.445312 1 1 1 h 4 c 0.554688 0 1 -0.445312 1 -1 v -4 c 0 -0.554688 -0.445312 -1 -1 -1 z m 0 1 h 2 v 4 h -2 z m 0 0" fill="#222222"/></svg>

Before

Width:  |  Height:  |  Size: 562 B

View File

@@ -1,49 +1,58 @@
# src/meson.build
# -------------------------------------------------
# 1⃣ Set up paths that Builder already used
# -------------------------------------------------
pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name()
moduledir = pkgdatadir / 'gnomeframe'
gnome = import('gnome')
blueprints = custom_target('blueprints',
input: files(
'ui/window.blp',
'ui/shortcuts-dialog.blp',
'ui/checklist.blp',
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT', '@CURRENT_SOURCE_DIRECTORY@' , '@INPUT@'],
)
gnome.compile_resources('gnomeframe',
'gnomeframe.gresource.xml',
gresource_bundle: true,
install: true,
install_dir: pkgdatadir,
dependencies: blueprints,
)
moduledir = pkgdatadir / 'gnomeframe' # <-- this is where the *data* files go
# (the GResource generated in data/ will be installed there by the toplevel file)
# -------------------------------------------------
# 2⃣ Python helper
# -------------------------------------------------
python = import('python')
py_inst = python.find_installation('python3')
# -------------------------------------------------
# 3⃣ Configuration data (kept exactly as you had it)
# -------------------------------------------------
conf = configuration_data()
conf.set('PYTHON', python.find_installation('python3').full_path())
conf.set('PYTHON', py_inst.full_path())
conf.set('VERSION', meson.project_version())
conf.set('localedir', get_option('prefix') / get_option('localedir'))
conf.set('pkgdatadir', pkgdatadir)
configure_file(
input: 'gnomeframe.in',
input : 'gnomeframe.in',
output: 'gnomeframe',
configuration: conf,
install: true,
install_dir: get_option('bindir'),
install_mode: 'rwxr-xr-x'
configuration : conf,
install : true,
install_dir : get_option('bindir'), # the launcher script goes to /app/bin
install_mode : 'rwxr-xr-x'
)
gnomeframe_sources = [
'__init__.py',
'main.py',
'window.py',
]
# -------------------------------------------------
# 4⃣ Install the **Python package**
# -------------------------------------------------
# The package lives in src/gnomeframe/ (notice the extra directory level)
# We install the whole directory tree into the Python sitepackages location.
# Meson will create the correct destination, e.g.
# /app/lib/python3.11/site-packages/gnomeframe/
install_subdir(
'gnomeframe', # <-- source directory (relative to src/)
install_dir : py_inst.get_install_dir(), # sitepackages
)
install_data(gnomeframe_sources, install_dir: moduledir)
# -------------------------------------------------
# 5⃣ (Optional) Install any *nonPython* data that still lives
# in src/ (e.g. .desktop files, icons that you kept here)
# -------------------------------------------------
# If you have such files, list them here and install them to `moduledir`.
# Example (uncomment if you need it):
# data_files = ['some-icon.svg', 'org.example.myapp.desktop']
# install_data(data_files,
# install_dir : moduledir)
# -------------------------------------------------
# 6⃣ Nothing else to change the toplevel meson file
# already builds the GResource from data/resources/.
# -------------------------------------------------

View File

@@ -1,31 +0,0 @@
.disabled {
background: rgb(29,29,29);
}
.toggle:checked {
background: var(--accent-bg-color);
}
.toggle:hover {
background: color-mix(in srgb, #222226 ,var(--accent-color) 20%);
}
.toggle:active {
background: var(--accent-color);
}
flowboxchild:hover {
background: #222222;
}
.flowbox:active {
background: #222222;
}
.box:active {
background: #222222;
}
.bold {
font-weight: bold;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
using Gtk 4.0;
using Adw 1;
Adw.ShortcutsDialog {
Adw.ShortcutsSection {
title: _("Shortcuts");
Adw.ShortcutsItem {
title: _("Show Shortcuts");
action-name: "app.shortcuts";
}
Adw.ShortcutsItem {
title: _("Quit");
action-name: "app.quit";
}
}
}

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="AdwShortcutsDialog">
<child>
<object class="AdwShortcutsSection">
<property name="title" translatable="yes">Shortcuts</property>
<child>
<object class="AdwShortcutsItem">
<property name="title" translatable="yes">Show Shortcuts</property>
<property name="action-name">app.shortcuts</property>
</object>
</child>
<child>
<object class="AdwShortcutsItem">
<property name="title" translatable="yes">Quit</property>
<property name="action-name">app.quit</property>
</object>
</child>
</object>
</child>
</object>
</interface>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff