backend rewrite cont, rebrand

This commit is contained in:
2025-12-06 00:41:38 +01:00
parent 8428efdbce
commit e651c8e18a
33 changed files with 498 additions and 428 deletions

View File

@@ -1,4 +1,4 @@
# Gnomeframe
# Void Manifest
This is a companion-app for Warframe players on Linux, written in phython and GTK4/ADW with the help of [GNOME Builder](https://apps.gnome.org/Builder/) and [PyGObject](https://pygobject.gnome.org/).
@@ -18,7 +18,7 @@ This is a companion-app for Warframe players on Linux, written in phython and GT
Ideally, cloning the repo and opening with GNOME Builder will have the best results since that is the development environment I am using.
```
$ git clone https://git.valhrafnaz.gay/valhrafnaz/Gnomeframe.git
$ git clone https://git.valhrafnaz.gay/valhrafnaz/VoidManifest.git
```
Alternatively, you can run ninja yourself.

View File

@@ -1,7 +1,7 @@
[Desktop Entry]
Name=Gnomeframe
Name=VoidManifest
Exec=gnomeframe
Icon=gay.valhrafnaz.Gnomeframe
Icon=gay.valhrafnaz.Voidmanifest
Terminal=false
Type=Application
Categories=Utility;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="gnomeframe">
<schema id="gay.valhrafnaz.Gnomeframe" path="/gay/valhrafnaz/Gnomeframe/">
<schemalist gettext-domain="voidmanifest">
<schema id="gay.valhrafnaz.Voidmanifest" path="/gay/valhrafnaz/Voidmanifest/">
<key name="window-width" type="i">
<default>800</default>
</key>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>gay.valhrafnaz.Gnomeframe</id>
<id>gay.valhrafnaz.VoidManifest</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>AGPL-3.0-or-later</project_license>
<name>Gnomeframe</name>
<name>VoidManifest</name>
<summary>A Warframe Companion</summary>
<description>
<p>This is a companion-app for Warframe players on Linux, written in phython and GTK4/ADW with the help of GNOME Builder and PyGObject.</p>
@@ -17,26 +17,26 @@
<!-- Required: Should be a link to the upstream homepage for the component -->
<url type="homepage">https://valhrafnaz.gay/gnomeframe</url>
<!-- Recommended: It is highly recommended for open-source projects to display the source code repository -->
<url type="vcs-browser">https://git.valhrafnaz.gay/valhrafnaz/Gnomeframe</url>
<url type="vcs-browser">https://git.valhrafnaz.gay/valhrafnaz/VoidManifest</url>
<!-- Should point to the software's bug tracking system, for users to report new bugs -->
<url type="bugtracker">https://git.valhrafnaz.gay/valhrafnaz/Gnomeframe/issues</url>
<url type="bugtracker">https://git.valhrafnaz.gay/valhrafnaz/VoidManifest/issues</url>
<!-- Should link a FAQ page for this software, to answer some of the most-asked questions in detail -->
<!-- URLs of this type should point to a webpage where users can submit or modify translations of the upstream project -->
<url type="translate">https://valhrafnaz.gay/gnomeframe/translate</url>
<url type="faq">https://valhrafnaz.gay/gnomeframe/faq</url>
<!-- Should provide a web link to an online user's reference, a software manual or help page -->
<url type="help">https://git.valhrafnaz.gay/valhrafnaz/Gnomeframe/wiki</url>
<url type="help">https://git.valhrafnaz.gay/valhrafnaz/VoidManifest/wiki</url>
<!-- URLs of this type should point to a webpage showing information on how to donate to the described software project -->
<url type="donation">https://valhrafnaz.gay/donate</url>
<!-- This could for example be an HTTPS URL to an online form or a page describing how to contact the developer -->
<url type="contact">https://valhrafnaz.gay/gnomeframe</url>
<!-- URLs of this type should point to a webpage showing information on how to contribute to the described software project -->
<url type="contribute">https://git.valhrafnaz.gay/valhrafnaz/Gnomeframe</url>
<url type="contribute">https://git.valhrafnaz.gay/valhrafnaz/VoidManifest</url>
<translation type="gettext">gnomeframe</translation>
<!-- All graphical applications having a desktop file must have this tag in the MetaInfo.
If this is present, appstreamcli compose will pull icons, keywords and categories from the desktop file. -->
<launchable type="desktop-id">gay.valhrafnaz.Gnomeframe.desktop</launchable>
<launchable type="desktop-id">gay.valhrafnaz.VoidManifest.desktop</launchable>
<!-- Use the OARS website (https://hughsie.github.io/oars/generate.html) to generate these and make sure to use oars-1.1 -->
<content_rating type="oars-1.1" />

View File

@@ -1,3 +1,3 @@
[D-BUS Service]
Name=gay.valhrafnaz.Gnomeframe
Name=gay.valhrafnaz.VoidManifest
Exec=@bindir@/gnomeframe --gapplication-service

View File

@@ -1,6 +1,6 @@
desktop_file = i18n.merge_file(
input: 'gay.valhrafnaz.Gnomeframe.desktop.in',
output: 'gay.valhrafnaz.Gnomeframe.desktop',
input: 'gay.valhrafnaz.VoidManifest.desktop.in',
output: 'gay.valhrafnaz.VoidManifest.desktop',
type: 'desktop',
po_dir: '../po',
install: true,
@@ -13,8 +13,8 @@ if desktop_utils.found()
endif
appstream_file = i18n.merge_file(
input: 'gay.valhrafnaz.Gnomeframe.metainfo.xml.in',
output: 'gay.valhrafnaz.Gnomeframe.metainfo.xml',
input: 'gay.valhrafnaz.VoidManifest.metainfo.xml.in',
output: 'gay.valhrafnaz.VoidManifest.metainfo.xml',
po_dir: '../po',
install: true,
install_dir: get_option('datadir') / 'metainfo'
@@ -24,7 +24,7 @@ appstreamcli = find_program('appstreamcli', required: false, disabler: true)
test('Validate appstream file', appstreamcli,
args: ['validate', '--no-net', '--explain', appstream_file])
install_data('gay.valhrafnaz.Gnomeframe.gschema.xml',
install_data('gay.valhrafnaz.VoidManifest.gschema.xml',
install_dir: get_option('datadir') / 'glib-2.0' / 'schemas'
)
@@ -37,8 +37,8 @@ test('Validate schema file',
service_conf = configuration_data()
service_conf.set('bindir', get_option('prefix') / get_option('bindir'))
configure_file(
input: 'gay.valhrafnaz.Gnomeframe.service.in',
output: 'gay.valhrafnaz.Gnomeframe.service',
input: 'gay.valhrafnaz.VoidManifest.service.in',
output: 'gay.valhrafnaz.VoidManifest.service',
configuration: service_conf,
install_dir: get_option('datadir') / 'dbus-1' / 'services'
)

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/gay/valhrafnaz/Gnomeframe/">
<gresource prefix="/gay/valhrafnaz/VoidManifest/">
<file>style.css</file>
<file preprocess="xml-stripblanks">ui/window.ui</file>
<file preprocess="xml-stripblanks">ui/shortcuts-dialog.ui</file>
@@ -9,7 +9,7 @@
<file preprocess="xml-stripblanks">ui/settings.ui</file>
<file preprocess="xml-stripblanks">ui/welcome.ui</file>
</gresource>
<gresource prefix="/gay/valhrafnaz/Gnomeframe/">
<gresource prefix="/gay/valhrafnaz/VoidManifest/">
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/floppy-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/check-round-outline2-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/compass2-symbolic.svg</file>

View File

@@ -1,4 +1,4 @@
application_id = 'gay.valhrafnaz.Gnomeframe'
application_id = 'gay.valhrafnaz.VoidManifest'
scalable_dir = 'hicolor' / 'scalable' / 'apps'
install_data(

View File

@@ -24,7 +24,7 @@ template $ChecklistPage: Box {
}
Label {
label: _("List of all Warframes available as of Nov '25");
label: _("List of all Warframes available as of Dec 10 '25");
margin-bottom: 12;
styles [
@@ -62,7 +62,13 @@ template $ChecklistPage: Box {
]
}
Label {
label: "/62";
label: "/";
margin-bottom: 12;
styles [
"dimmed",
]
}
Label basic_max {
margin-bottom: 12;
styles [
"dimmed",
@@ -514,7 +520,14 @@ template $ChecklistPage: Box {
]
}
Label {
label: "/49";
label: "/";
margin-bottom: 12;
styles [
"dimmed",
]
}
Label prime_max {
label: "";
margin-bottom: 12;
styles [
"dimmed",

View File

@@ -32,7 +32,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">List of all Warframes available as of Nov '25</property>
<property name="label" translatable="yes">List of all Warframes available as of Dec 10 '25</property>
<property name="margin-bottom">12</property>
<style>
<class name="dim-label"/>
@@ -76,7 +76,15 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<object class="GtkLabel">
<property name="label">/62</property>
<property name="label">/</property>
<property name="margin-bottom">12</property>
<style>
<class name="dimmed"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="basic_max">
<property name="margin-bottom">12</property>
<style>
<class name="dimmed"/>
@@ -609,7 +617,16 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<object class="GtkLabel">
<property name="label">/49</property>
<property name="label">/</property>
<property name="margin-bottom">12</property>
<style>
<class name="dimmed"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="prime_max">
<property name="label"></property>
<property name="margin-bottom">12</property>
<style>
<class name="dimmed"/>

View File

@@ -5,16 +5,54 @@ template $HomePage: Box {
orientation: vertical;
baseline-position: center;
Adw.Clamp {
Adw.StatusPage {
title: _("Home");
Box {
baseline-position: center;
orientation: vertical;
Label {
justify: center;
wrap: true;
wrap-mode: word;
label: _("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui.");
Adw.PreferencesGroup {
title: _("Welcome back, Tenno");
Adw.ActionRow {
title: _("Owned Unique Frames:");
title-selectable: false;
activatable: false;
[suffix]
Label owned_frames {
}
}
Adw.ActionRow {
title: _("Owned Basic Frames:");
title-selectable: false;
activatable: false;
[suffix]
Label owned_basics {
}
}
Adw.ActionRow {
title: _("Missing Basic Frames:");
title-selectable: false;
activatable: false;
[suffix]
Label missing_basics {
}
}
Adw.ActionRow {
title: _("Owned Prime Frames:");
title-selectable: false;
activatable: false;
[suffix]
Label owned_primes {
}
}
Adw.ActionRow {
title: _("Missing Prime Frames:");
title-selectable: false;
activatable: false;
[suffix]
Label missing_primes {
}
}
}

View File

@@ -12,19 +12,55 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child>
<object class="AdwClamp">
<child>
<object class="AdwStatusPage">
<property name="title" translatable="yes">Home</property>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Welcome back, Tenno</property>
<child>
<object class="GtkBox">
<property name="baseline-position">1</property>
<property name="orientation">1</property>
<child>
<object class="GtkLabel">
<property name="justify">2</property>
<property name="wrap">true</property>
<property name="wrap-mode">0</property>
<property name="label" translatable="yes">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui.</property>
</object>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Unique Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_frames"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Missing Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_primes"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Missing Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_primes"></object>
</child>
</object>
</child>

View File

@@ -2,8 +2,8 @@ using Gtk 4.0;
using Adw 1;
Adw.Dialog {
title: _("Welcome to Gnomeframe!");
title: _("Welcome to VoidManifest!");
Text {
text: _("Thank you for installing Gnomeframe! Please note that this is a fan project. Digital Extremes Ltd, Warframe and the logo Warframe are registered trademarks. All rights are reserved worldwide. This application has no official link with Digital Extremes Ltd or Warframe. All artwork, screenshots, characters or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Digital Extremes Ltd.");
text: _("Thank you for installing VoidManifest! Please note that this is a fan project. Digital Extremes Ltd, Warframe and the logo Warframe are registered trademarks. All rights are reserved worldwide. This application has no official link with Digital Extremes Ltd or Warframe. All artwork, screenshots, characters or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Digital Extremes Ltd.");
}
}

View File

@@ -7,11 +7,11 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<interface>
<requires lib="gtk" version="4.0"/>
<object class="AdwDialog">
<property name="title" translatable="yes">Welcome to Gnomeframe!</property>
<property name="title" translatable="yes">Welcome to VoidManifest!</property>
<child>
<object class="GtkText">
<property name="text" translatable="yes">Thank you for installing Gnomeframe! Please note that this is a fan project. Digital Extremes Ltd, Warframe and the logo Warframe are registered trademarks. All rights are reserved worldwide. This application has no official link with Digital Extremes Ltd or Warframe. All artwork, screenshots, characters or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Digital Extremes Ltd.</property>
<property name="text" translatable="yes">Thank you for installing VoidManifest! Please note that this is a fan project. Digital Extremes Ltd, Warframe and the logo Warframe are registered trademarks. All rights are reserved worldwide. This application has no official link with Digital Extremes Ltd or Warframe. All artwork, screenshots, characters or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Digital Extremes Ltd.</property>
</object>
</child>
</object>
</interface>
</interface>

View File

@@ -1,10 +1,10 @@
using Gtk 4.0;
using Adw 1;
template $GnomeframeWindow: Adw.ApplicationWindow {
template $VoidManifestWindow: Adw.ApplicationWindow {
default-width: 800;
default-height: 600;
title: "Gnomeframe";
title: "VoidManifest";
content:
// Scaffolding
@@ -66,7 +66,7 @@ menu primary_menu {
}
item {
label: _("_About Gnomeframe");
label: _("_About VoidManifest");
action: "app.about";
}
}

View File

@@ -1,10 +1,10 @@
using Gtk 4.0;
using Adw 1;
template $GnomeframeWindow: Adw.ApplicationWindow {
template $VoidManifestWindow: Adw.ApplicationWindow {
default-width: 800;
default-height: 600;
title: "Gnomeframe";
title: "VoidManifest";
content:
Adw.ToolbarView {
@@ -1053,7 +1053,7 @@ menu primary_menu {
}
item {
label: _("_About Gnomeframe");
label: _("_About VoidManifest");
action: "app.about";
}
}

View File

@@ -6,10 +6,10 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="GnomeframeWindow" parent="AdwApplicationWindow">
<template class="VoidManifestWindow" parent="AdwApplicationWindow">
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="title">Gnomeframe</property>
<property name="title">VoidManifest</property>
<property name="content">
<object class="AdwToolbarView">
<property name="bottom-bar-style">1</property>
@@ -70,9 +70,9 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<attribute name="action">app.help</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About Gnomeframe</attribute>
<attribute name="label" translatable="yes">_About VoidManifest</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>
</interface>

View File

View File

@@ -1,5 +1,5 @@
{
"id" : "gay.valhrafnaz.Gnomeframe",
"id" : "gay.valhrafnaz.VoidManifest",
"runtime" : "org.gnome.Platform",
"runtime-version" : "49",
"sdk" : "org.gnome.Sdk",
@@ -24,13 +24,13 @@
],
"modules" : [
{
"name" : "Gnomeframe",
"name" : "VoidManifest",
"builddir" : true,
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"url" : "file:///home/nihil/Coding"
"url" : "https://git.valhrafnaz.gay/valhrafnaz/VoidManifest.git"
}
]
}

View File

@@ -1,5 +1,5 @@
project('gnomeframe',
version: '0.1.0',
project('voidmanifest',
version: '1.0.1',
meson_version: '>= 1.0.0',
default_options: [ 'warning_level=2', 'werror=false', ],
)

View File

@@ -1,67 +0,0 @@
pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name()
gnome = import('gnome')
blueprints = custom_target('blueprints',
input: files(
'window.blp'
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '--minify', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
)
gnome.compile_resources('gnomeframe',
'gnomeframe.gresource.xml',
gresource_bundle: true,
install: true,
install_dir: pkgdatadir,
dependencies: blueprints,
)
conf = configuration_data()
conf.set_quoted('VERSION', meson.project_version())
conf.set_quoted('GETTEXT_PACKAGE', 'gnomeframe')
conf.set_quoted('LOCALEDIR', get_option('prefix') / get_option('localedir'))
conf.set_quoted('PKGDATADIR', pkgdatadir)
configure_file(
input: 'config.rs.in',
output: 'config.rs',
configuration: conf
)
# Copy the config.rs output to the source directory.
run_command(
'cp',
meson.project_build_root() / 'src' / 'config.rs',
meson.project_source_root() / 'src' / 'config.rs',
check: true
)
cargo_bin = find_program('cargo')
cargo_opt = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
cargo_opt += [ '--target-dir', meson.project_build_root() / 'src' ]
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ]
if get_option('buildtype') == 'release'
cargo_opt += [ '--release' ]
rust_target = 'release'
else
rust_target = 'debug'
endif
cargo_build = custom_target(
'cargo-build',
build_by_default: true,
build_always_stale: true,
output: meson.project_name(),
console: true,
install: true,
install_dir: get_option('bindir'),
command: [
'env', cargo_env,
cargo_bin, 'build',
cargo_opt, '&&', 'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
]
)

View File

@@ -1,8 +1,8 @@
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
data/gay.valhrafnaz.Gnomeframe.desktop.in
data/gay.valhrafnaz.Gnomeframe.metainfo.xml.in
data/gay.valhrafnaz.Gnomeframe.gschema.xml
data/gay.valhrafnaz.VoidManifest.desktop.in
data/gay.valhrafnaz.VoidManifest.metainfo.xml.in
data/gay.valhrafnaz.VoidManifest.gschema.xml
src/main.py
src/window.py
src/window.ui

View File

@@ -6,66 +6,24 @@ import json
import os
import math
from pathlib import Path, cwd
from dataclasses import dataclass
from gi.repository import Gio, GLib, GObject
# pseudo struct that contains the profile data that gets serialized into the .json profile.
@dataclass
class Profile:
# Dictionary that stores the ownership status of frames
owned_frames: [str, bool]
unique_frames_owned: int
missing_basics: [str]
missing_basics_count: int
missing_primes: [str]
missing_primes_count: int
class Backend():
# warframe counts
# TODO Update via API/wiki
BASIC_WARFRAMES = 63
PRIME_WARFRAMES = 49
CURRENT_UPDATE = 41
# Profile
profile: Profile
# tracks whether the onboarding dialogue should be shown
fresh_install = True
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)
return cfg_dir
# helpers for profile loading
APP_ID = "gay.valhrafnaz.Gnomeframe"
CONFIG_DIR = get_config_dir(APP_ID)
CONFIG_FILE = CONFIG_DIR / "profile.json"
def __init__():
loaded_profile: dict[str, bool] = Backend.load_profile()
super().__init__()
# load profile
def load_profile() -> dict[str, bool]:
try:
with Backend.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:
Backend.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
with Backend.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}")

123
src/gnomeframe/constants.py Normal file
View File

@@ -0,0 +1,123 @@
EXISTING_FRAMES_BASIC = [
"ash",
"atlas",
"banshee",
"baruuk",
"caliban",
"chroma",
"citrine",
"cyte09",
"dagath",
"dante",
"ember",
"equinox",
"excalibur",
"frost",
"gara",
"garuda",
"gauss",
"grendel",
"gyre",
"harrow",
"hildryn",
"hydroid",
"inaros",
"ivara",
"jade",
"khora",
"koumei",
"kullervo",
"lavos",
"limbo",
"loki",
"mag",
"mesa",
"mirage",
"nekros",
"nezha",
"nidus",
"nokko",
"nova",
"nyx",
"oberon",
"octavia",
"oraxia",
"protea",
"qorvex",
"revenant",
"rhino",
"saryn",
"sevagoth",
"styanax",
"temple",
"titania",
"trinity",
"uriel",
"valkyr",
"vauban",
"volt",
"voruna",
"wisp",
"wukong",
"xaku",
"yareli",
"zephyr",
]
EXISTING_FRAMES_PRIME = [
"ash_prime",
"atlas_prime",
"banshee_prime",
"baruuk_prime",
"caliban_prime",
"chroma_prime",
"ember_prime",
"equinox_prime",
"excalibur_umbra",
"frost_prime",
"gara_prime",
"garuda_prime",
"gauss_prime",
"grendel_prime",
"gyre_prime",
"harrow_prime",
"hildryn_prime",
"hydroid_prime",
"inaros_prime",
"ivara_prime",
"khora_prime",
"lavos_prime",
"limbo_prime",
"loki_prime",
"mag_prime",
"mesa_prime",
"mirage_prime",
"nekros_prime",
"nezha_prime",
"nidus_prime",
"nova_prime",
"nyx_prime",
"oberon_prime",
"octavia_prime",
"protea_prime",
"revenant_prime",
"rhino_prime",
"saryn_prime",
"sevagoth_prime",
"titania_prime",
"trinity_prime",
"valkyr_prime",
"vauban_prime",
"volt_prime",
"wisp_prime",
"wukong_prime",
"xaku_prime",
"yareli_prime",
"zephyr_prime"
]
# warframe counts
# TODO Update via API/wiki
BASIC_WARFRAMES = 63
PRIME_WARFRAMES = 49
CURRENT_UPDATE = 41

View File

@@ -1,6 +1,6 @@
# main.py
#
# Copyright 2025 nihil
# Copyright 2025 valhrafnaz
#
# 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
@@ -16,24 +16,57 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import sys
import gi
import os
import json
import math
from pathlib import Path
# local imports
from .window import VoidManifestWindow
from .profile import Profile
from .constants import EXISTING_FRAMES_BASIC, EXISTING_FRAMES_PRIME, BASIC_WARFRAMES, PRIME_WARFRAMES, CURRENT_UPDATE
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gio, Adw, GLib
from .window import GnomeframeWindow
from gi.repository import Gtk, Gio, Adw, GLib, GObject # noqa: E402
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)
return cfg_dir
class GnomeframeApplication(Adw.Application):
class VoidManifestApplication(Adw.Application):
# Profile
profile: Profile
# tracks whether the onboarding dialogue should be shown
fresh_install = True
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)
return cfg_dir
# helpers for profile loading
APP_ID = "gay.valhrafnaz.VoidManifest"
CONFIG_DIR = get_config_dir(APP_ID)
CONFIG_FILE = CONFIG_DIR / "profile.json"
"""The main application singleton class."""
def __init__(self):
super().__init__(application_id='gay.valhrafnaz.Gnomeframe',
super().__init__(application_id='gay.valhrafnaz.VoidManifest',
flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
resource_base_path='/gay/valhrafnaz/Gnomeframe')
resource_base_path='/gay/valhrafnaz/VoidManifest')
data_dir = GLib.get_system_data_dirs()[0]
resource = Gio.resource_load(os.path.join(data_dir, 'gnomeframe', 'gnomeframe.gresource'))
@@ -43,6 +76,10 @@ class GnomeframeApplication(Adw.Application):
self.create_action('about', self.on_about_action)
self.create_action('preferences', self.on_preferences_action)
self.CONFIG_DIR = get_config_dir(self.APP_ID)
self.profile = self.load_profile()
def do_activate(self):
"""Called when the application is activated.
@@ -51,17 +88,17 @@ class GnomeframeApplication(Adw.Application):
"""
win = self.props.active_window
if not win:
win = GnomeframeWindow(application=self)
win = VoidManifestWindow(application=self)
win.present()
def on_about_action(self, *args):
"""Callback for the app.about action."""
about = Adw.AboutDialog(application_name='gnomeframe',
application_icon='gay.valhrafnaz.Gnomeframe',
application_icon='gay.valhrafnaz.VoidManifest',
developer_name='valhrafnaz',
version='0.1.0',
developers=['valhrafnaz'],
copyright='© 2025 nihil')
copyright='© 2025 valhrafnaz')
# Translators: Replace "translator-credits" with your name/username, and optionally an email or URL.
about.set_translator_credits(_('translator-credits'))
about.present(self.props.active_window)
@@ -85,8 +122,69 @@ class GnomeframeApplication(Adw.Application):
if shortcuts:
self.set_accels_for_action(f"app.{name}", shortcuts)
def load_profile(self) -> Profile:
try:
with open(self.CONFIG_FILE, 'r') as f:
data = json.load(f)
return Profile(**data)
except (FileNotFoundError, json.JSONDecodeError):
return Profile() # Uses defaults
def save_profile(self, profile) -> None:
self.refresh_profile()
profile_path = self.CONFIG_FILE
with open(profile_path, 'w') as f:
json.dump(profile.to_dict(), f, indent=2)
def reset_frames(self, param):
self.profile.owned_frames = {}
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()
self.save_profile(self.profile)
def refresh_profile(self):
# count basics
basic_count = 0
missing_basics: list[str] = []
for basic_frame in EXISTING_FRAMES_BASIC:
if self.profile.owned_frames.get(basic_frame, False):
basic_count = basic_count + 1
else:
missing_basics.append(basic_frame)
# count primes
prime_count = 0
missing_primes: list[str] = []
for prime_frame in EXISTING_FRAMES_PRIME:
if self.profile.owned_frames.get(prime_frame, False):
prime_count = prime_count + 1
else:
missing_primes.append(prime_frame)
# count unique
unique_frames: set[str] = set()
for owned_frame, is_owned in self.profile.owned_frames.items():
if is_owned:
_name = owned_frame.removesuffix("_prime")
_name = _name.removesuffix("_umbra")
unique_frames.add(_name)
self.profile.missing_basics_count = len(missing_basics)
self.profile.missing_primes_count = len(missing_primes)
self.profile.unique_frames_owned = len(unique_frames)
self.profile.missing_basics = missing_basics
self.profile.missing_primes = missing_primes
def main(version):
"""The application's entry point."""
app = GnomeframeApplication()
app = VoidManifestApplication()
return app.run(sys.argv)

View File

@@ -1,168 +1,18 @@
import json
import os
import math
import gi
from pathlib import Path
from gi.repository import Gtk, Adw, GObject, 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)
return cfg_dir
from ..constants import BASIC_WARFRAMES, PRIME_WARFRAMES
APP_ID = "gay.valhrafnaz.Gnomeframe"
CONFIG_DIR = get_config_dir(APP_ID)
CONFIG_FILE = CONFIG_DIR / "profile.json"
TOTAL_WARFRAMES=63 # post uriel, valid until whenever really
TOTAL_PRIMES=49 # post gyre prime, valid until ca. may-2026
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject, GLib # noqa: E402
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}")
frame_button_ids = [
"ash",
"atlas",
"banshee",
"baruuk",
"caliban",
"chroma",
"citrine",
"cyte09",
"dagath",
"dante",
"ember",
"equinox",
"exalibur",
"frost",
"gara",
"garuda",
"gauss",
"grendel",
"gyre",
"harrow",
"hildryn",
"hydroid",
"inaros",
"ivara",
"jade",
"khora",
"koumei",
"kullervo",
"lavos",
"limbo",
"loki",
"mag",
"mesa",
"mirage" "nekros",
"nezha",
"nidus",
"nokko",
"nova",
"nyx",
"oberon",
"octavia",
"oraxia",
"protea",
"qorvex",
"revenant",
"rhino",
"saryn",
"sevagoth",
"styanax",
"temple",
"titania",
"trinity",
"valkyr",
"vauban",
"volt",
"voruna",
"wisp",
"wukong",
"xaku",
"yareli",
"zephyr",
"ash_prime",
"atlas_prime",
"banshee_prime",
"baruuk_prime",
"caliban_prime",
"chroma_prime",
"citrine_prime",
"cyte09_prime",
"dagath_prime",
"dante_prime",
"ember_prime",
"equinox_prime",
"exalibur_umbra",
"frost_prime",
"gara_prime",
"garuda_prime",
"gauss_prime",
"grendel_prime",
"gyre_prime",
"harrow_prime",
"hildryn_prime",
"hydroid_prime",
"inaros_prime",
"ivara_prime",
"jade_prime",
"khora_prime",
"koumei_prime",
"kullervo_prime",
"lavos_prime",
"limbo_prime",
"loki_prime",
"mag_prime",
"mesa_prime",
"mirage_prime" "nekros_prime",
"nezha_prime",
"nidus_prime",
"nokko_prime",
"nova_prime",
"nyx_prime",
"oberon_prime",
"octavia_prime",
"oraxia_prime",
"protea_prime",
"qorvex_prime",
"revenant_prime",
"rhino_prime",
"saryn_prime",
"sevagoth_prime",
"styanax_prime",
"temple_prime",
"titania_prime",
"trinity_prime",
"valkyr_prime",
"vauban_prime",
"volt_prime",
"voruna_prime",
"wisp_prime",
"wukong_prime",
"xaku_prime",
"yareli_prime",
"zephyr_prime",
]
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/checklist.ui")
@Gtk.Template(resource_path="/gay/valhrafnaz/VoidManifest/ui/checklist.ui")
class ChecklistPage(Gtk.Box):
__gtype_name__ = "ChecklistPage"
@@ -171,17 +21,21 @@ class ChecklistPage(Gtk.Box):
btns_basic = Gtk.Template.Child()
btns_prime = Gtk.Template.Child()
basic_counter = Gtk.Template.Child()
basic_max = Gtk.Template.Child()
basic_percent = Gtk.Template.Child()
prime_counter = Gtk.Template.Child()
prime_max = Gtk.Template.Child()
prime_percent = Gtk.Template.Child()
def __init__(self, *, parent: Adw.ViewStack, window):
super().__init__()
self._parent_window = window
self.app = self._parent_window.get_application()
parent.add_titled(self, "checklist", "Checklist")
wrapper = parent.get_page(self)
wrapper.set_icon_name("check-round-outline2-symbolic")
self.basic_max.set_label(str(BASIC_WARFRAMES))
self.prime_max.set_label(str(PRIME_WARFRAMES))
self._connect_frame_btns()
self._calc_frames()
@@ -190,14 +44,14 @@ class ChecklistPage(Gtk.Box):
while button is not None:
if isinstance(button, Gtk.ToggleButton):
btn_id = button.get_name()
button.set_active(self._parent_window._profile.get(btn_id, False))
button.set_active(self.app.profile.owned_frames.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._parent_window._profile.get(btn_id, False))
button.set_active(self.app.profile.owned_frames.get(btn_id, False))
button.connect("toggled", self._on_button_toggled)
button = button.get_next_sibling()
@@ -223,9 +77,9 @@ class ChecklistPage(Gtk.Box):
def _calc_frames(self):
basic_counter = self._count_basics()
basic_percent = math.trunc(basic_counter / TOTAL_WARFRAMES * 100)
basic_percent = math.trunc(basic_counter / BASIC_WARFRAMES * 100)
prime_counter = self._count_primes()
prime_percent = math.trunc(prime_counter / TOTAL_PRIMES * 100)
prime_percent = math.trunc(prime_counter / PRIME_WARFRAMES * 100)
self.basic_counter.set_label(str(basic_counter))
self.basic_percent.set_label(str(basic_percent))
self.prime_counter.set_label(str(prime_counter))
@@ -236,8 +90,8 @@ class ChecklistPage(Gtk.Box):
if btn_id == "GtkToggleButton":
print("what")
return
self._parent_window._profile[btn_id] = btn.get_active()
save_profile(self._parent_window._profile)
self.app.profile.owned_frames[btn_id] = btn.get_active()
self.app.save_profile(self.app.profile)
self._calc_frames()

View File

@@ -1,9 +1,23 @@
from gi.repository import Gtk, Adw, GObject, GLib
import gi
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/home.ui")
from ..constants import BASIC_WARFRAMES, PRIME_WARFRAMES
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject, GLib # noqa: E402
@Gtk.Template(resource_path="/gay/valhrafnaz/VoidManifest/ui/home.ui")
class HomePage(Gtk.Box):
__gtype_name__ = "HomePage"
owned_frames = Gtk.Template.Child()
owned_basics = Gtk.Template.Child()
missing_basics = Gtk.Template.Child()
owned_primes = Gtk.Template.Child()
missing_primes = Gtk.Template.Child()
def __init__(self, *, parent: Adw.ViewStack):
"""
`parent` is the ViewStack that will host this page.
@@ -14,3 +28,13 @@ class HomePage(Gtk.Box):
parent.add_titled(self, "home", "Home")
wrapper = parent.get_page(self)
wrapper.set_icon_name("compass2-symbolic")
self.app = self.get_root().get_application()
self.app.profile._on_change = self.refresh
self.refresh()
def refresh(self):
self.owned_frames.set_label(str (self.app.profile.unique_frames_owned))
self.owned_basics.set_label(str (BASIC_WARFRAMES - self.app.profile.missing_basics_count))
self.owned_primes.set_label(str (PRIME_WARFRAMES - self.app.profile.missing_primes_count))
self.missing_basics.set_label(str(self.app.profile.missing_basics_count))
self.missing_primes.set_label(str(self.app.profile.missing_primes_count))

View File

@@ -1,6 +1,6 @@
from gi.repository import Gtk, Adw, GObject, GLib
@Gtk.Template(resource_path="/gay/valhrafnaz/Gnomeframe/ui/settings.ui")
@Gtk.Template(resource_path="/gay/valhrafnaz/VoidManifest/ui/settings.ui")
class SettingsPage(Gtk.Box):
__gtype_name__ = "SettingsPage"

29
src/gnomeframe/profile.py Normal file
View File

@@ -0,0 +1,29 @@
from dataclasses import fields, field, dataclass
from typing import Callable
from .constants import BASIC_WARFRAMES, PRIME_WARFRAMES
# pseudo struct that contains the profile data that gets serialized into the .json profile.
@dataclass
class Profile:
owned_frames: dict[str, bool] = field(default_factory=dict)
unique_frames_owned: int = 0
missing_basics: list[str] = field(default_factory=list)
missing_basics_count: int = BASIC_WARFRAMES
missing_primes: list[str] = field(default_factory=list)
missing_primes_count: int = PRIME_WARFRAMES
_on_change: Callable | None = field(default=None, repr=False, compare=False)
def __setattr__(self, name, value):
super().__setattr__(name, value)
# Notify on change (skip private attributes)
if not name.startswith('_') and hasattr(self, '_on_change') and self._on_change:
self._on_change()
def to_dict(self) -> dict:
"""Convert to dict, excluding private fields"""
return {
f.name: getattr(self, f.name)
for f in fields(self)
if not f.name.startswith('_')
}

View File

@@ -29,85 +29,32 @@ 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)
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'
@Gtk.Template(resource_path='/gay/valhrafnaz/VoidManifest/ui/window.ui')
class VoidManifestWindow(Adw.ApplicationWindow):
__gtype_name__ = 'VoidManifestWindow'
viewstack = Gtk.Template.Child()
btn_reset_profile = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._profile: dict[str, bool] = load_profile()
self.app = self.get_application()
self._load_page_templates()
reset_action = Gio.SimpleAction.new("reset-selections", None)
reset_action.connect("activate", self._reset_all)
reset_action.connect("activate", self.app.reset_frames)
self.get_application().add_action(reset_action)
self.btn_reset_profile.connect("clicked", self._reset_all)
self.btn_reset_profile.connect("clicked", self.app.reset_frames)
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.checklist_page = ChecklistPage(parent=self.viewstack, window=self)
self.settings_page = SettingsPage(parent=self.viewstack)
def _reset_all(self, 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)
self.app.save_profile(self.app.profile)
return False

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB