mirror of
https://github.com/Cheviiot/Vual.git
synced 2026-08-03 15:57:25 +00:00
v0.2.0
This commit is contained in:
@@ -1,98 +1,137 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="data/Vual.png" alt="Vual" width="96">
|
<img src="data/Vual.png" alt="Vual" width="128">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">Vual</h1>
|
<h1 align="center">Vual</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
GTK4-приложение для запуска Cheat Engine в Steam-играх через Proton
|
Запуск Cheat Engine для Steam-игр через Proton
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-GPL--3.0-green?style=flat-square" alt="GPL-3.0"></a>
|
<img src="https://img.shields.io/badge/GTK-4-4a86cf?style=flat-square" alt="GTK 4">
|
||||||
<img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square" alt="Python 3.11+">
|
<img src="https://img.shields.io/badge/Libadwaita-1-3584e4?style=flat-square" alt="Libadwaita">
|
||||||
<img src="https://img.shields.io/badge/gtk-4-4a86cf?style=flat-square" alt="GTK 4">
|
<img src="https://img.shields.io/badge/Python-3.11+-3776ab?style=flat-square" alt="Python 3.11+">
|
||||||
<img src="https://img.shields.io/badge/libadwaita-1-3584e4?style=flat-square" alt="Libadwaita 1">
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0-green?style=flat-square" alt="GPL-3.0"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Что это
|
|
||||||
|
|
||||||
Vual показывает библиотеку установленных Steam-игр в виде сетки с обложками и позволяет в один клик запустить игру, включить для неё [protonhax](https://github.com/jcnils/protonhax) и открыть Cheat Engine прямо внутри Proton-контекста запущенной игры.
|
## О проекте
|
||||||
|
|
||||||
Приложение самостоятельно скачивает и распаковывает Cheat Engine, управляет protonhax, настраивает тему Wine для всех Proton-префиксов и поддерживает русский язык — как собственного интерфейса, так и Cheat Engine.
|
Vual — приложение для запуска Cheat Engine в Steam-играх, работающих через Proton.
|
||||||
|
Показывает библиотеку Steam, управляет protonhax и launch options, скачивает и
|
||||||
|
распаковывает CE автоматически, запускает его в нужном Wine-префиксе — всё из одного
|
||||||
|
окна.
|
||||||
|
|
||||||
## Возможности
|
## Возможности
|
||||||
|
|
||||||
| | |
|
- **Сетка игр** — обложки из Steam CDN, поиск в реальном времени, сортировка по имени или статусу
|
||||||
|---|---|
|
- **Запуск одним кликом** — игра стартует через Steam, CE подключается к нужному Proton-префиксу
|
||||||
| 🎮 **Библиотека** | Сетка игр с обложками из Steam CDN, поиск, сортировка по имени и статусу |
|
- **Таблицы CE** — привязка .CT-файла к игре, автоматическое открытие вместе с CE
|
||||||
| ▶️ **Запуск** | Запуск игры через Steam URI, автоматическое подключение CE к запущенной Proton-игре |
|
- **protonhax** — автоматическая установка, переключатель launch options для каждой игры, массовое вкл/выкл
|
||||||
| 🔧 **protonhax** | Переключатель launch options для каждой игры и массовое включение/отключение |
|
- **Установка CE** — скачивание с cheatengine.org, распаковка через Wine из Proton
|
||||||
| 📦 **Cheat Engine** | Скачивание с cheatengine.org, распаковка через Wine из Proton, тестовый запуск |
|
- **Тема Wine** — тёмная или светлая тема для всех Proton-префиксов разом
|
||||||
| 🎨 **Тема Wine** | Тёмная и светлая тема для всех Proton-префиксов разом |
|
- **Локализация** — интерфейс EN/RU, язык CE, установка русской локализации из офиц. репозитория CE
|
||||||
| 🌐 **Локализация** | Язык приложения (EN/RU), язык CE, установка русской локализации CE |
|
- **Прозрачность окна** — полупрозрачный фон в стиле frosted glass
|
||||||
| ⚙️ **Настройки** | Путь к Steam, шаблон LaunchOptions, regex-исключения, размер плитки |
|
- **Гайд** — встроенная страница быстрого старта при первом запуске и из меню
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
|
### Stapler
|
||||||
|
|
||||||
|
```
|
||||||
|
stplr repo add lumenara https://github.com/Cheviiot/Lumenara.git
|
||||||
|
stplr refresh && stplr install vual
|
||||||
|
```
|
||||||
|
|
||||||
### Из исходников
|
### Из исходников
|
||||||
|
|
||||||
```bash
|
```
|
||||||
git clone https://github.com/Cheviiot/vual.git
|
git clone https://github.com/Cheviiot/vual.git
|
||||||
cd vual
|
cd vual
|
||||||
./vual
|
./vual
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Stapler](https://stplr.dev/)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
stplr repo add lumenara https://github.com/Cheviiot/Lumenara
|
|
||||||
stplr refresh && stplr install vual
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>Зависимости</strong></summary>
|
<summary><strong>Зависимости</strong></summary>
|
||||||
|
|
||||||
| Дистрибутив | Команда |
|
| Дистрибутив | Команда |
|
||||||
|---|---|
|
|---|---|
|
||||||
| ALT Linux | `apt-get install python3-module-pygobject3 libgtk4 libadwaita` |
|
| ALT Linux | `apt-get install python3-module-pygobject3 libgtk4 libadwaita python3-module-requests` |
|
||||||
| Fedora | `dnf install python3-gobject gtk4 libadwaita` |
|
| Fedora | `dnf install python3-gobject gtk4 libadwaita python3-requests` |
|
||||||
| Arch | `pacman -S python-gobject gtk4 libadwaita` |
|
| Arch | `pacman -S python-gobject gtk4 libadwaita python-requests` |
|
||||||
| Debian / Ubuntu | `apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1` |
|
| Debian / Ubuntu | `apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 python3-requests` |
|
||||||
|
|
||||||
Также необходим **Proton** (установленный через Steam) — для распаковки CE и работы protonhax.
|
Также нужен **Proton** (через Steam) — для распаковки CE и работы protonhax.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Быстрый старт
|
## Использование
|
||||||
|
|
||||||
1. Запустите `./vual`
|
|
||||||
2. Откройте настройки (☰ → Настройки)
|
|
||||||
3. На вкладке **Cheat Engine** скачайте и распакуйте CE
|
|
||||||
4. Установите **protonhax** если он ещё не установлен
|
|
||||||
5. Включите переключатель у нужной игры — Vual пропишет launch options
|
|
||||||
6. Нажмите ▶ у игры — после запуска CE откроется автоматически
|
|
||||||
|
|
||||||
## Структура данных
|
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.config/vual/
|
./vual
|
||||||
└── config.json # Настройки приложения
|
|
||||||
|
|
||||||
~/.local/share/vual/
|
|
||||||
├── cheatengine/ # Установленный Cheat Engine
|
|
||||||
├── bin/protonhax # Управляемый скрипт protonhax
|
|
||||||
└── wine_prefix/ # Префикс Wine для тестового запуска CE
|
|
||||||
|
|
||||||
~/.cache/vual/
|
|
||||||
└── covers/ # Кэш обложек Steam
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
1. При первом запуске откроется встроенный гайд
|
||||||
|
2. Перейди в **Настройки → Cheat Engine** — скачай и распакуй CE
|
||||||
|
3. Установи **protonhax** там же
|
||||||
|
4. Включи переключатель у нужной игры — Vual пропишет launch options
|
||||||
|
5. Нажми ▶ — игра запустится, CE откроется автоматически
|
||||||
|
|
||||||
|
## Управление
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Запуск CE | Клик по кнопке ▶ на плитке игры |
|
||||||
|
| Поиск | Клик по полю поиска в заголовке |
|
||||||
|
| Привязать таблицу | Правая кнопка мыши → Assign Table |
|
||||||
|
| Вкл/выкл protonhax | Переключатель на плитке игры |
|
||||||
|
| Массовое вкл/выкл | Кнопки Enable All / Disable All |
|
||||||
|
| Сортировка | Меню сортировки в заголовке |
|
||||||
|
| Настройки | Главное меню → Preferences |
|
||||||
|
| Гайд | Главное меню → Guide |
|
||||||
|
|
||||||
|
## Настройки
|
||||||
|
|
||||||
|
| | | По умолчанию |
|
||||||
|
|---|---|---|
|
||||||
|
| Тема | Системная / Светлая / Тёмная | Системная |
|
||||||
|
| Язык | Авто / English / Русский | Авто |
|
||||||
|
| Размер плиток | Small / Medium / Large | Medium |
|
||||||
|
| Сортировка | По имени / По статусу | По имени |
|
||||||
|
| Прозрачность окна | вкл / выкл | выкл |
|
||||||
|
| Тема Wine | Тёмная / Светлая | — |
|
||||||
|
| Язык CE | EN / RU | — |
|
||||||
|
|
||||||
|
## Данные
|
||||||
|
|
||||||
|
`~/.config/vual/`
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| config.json | Настройки |
|
||||||
|
|
||||||
|
`~/.local/share/vual/`
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| cheatengine/ | Cheat Engine |
|
||||||
|
| bin/protonhax | protonhax |
|
||||||
|
| tables/ | .CT-таблицы и привязки |
|
||||||
|
| wine_prefix/ | Wine-префикс для тестового запуска CE |
|
||||||
|
|
||||||
|
`~/.cache/vual/`
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| covers/ | Кэш обложек Steam |
|
||||||
|
|
||||||
## Участие в разработке
|
## Участие в разработке
|
||||||
|
|
||||||
> **Это личный проект.** Vual создаётся одним человеком для собственного использования при помощи ИИ.
|
> Это личный проект. Vual создаётся одним человеком для собственного использования при помощи ИИ.
|
||||||
|
|
||||||
Код полностью открыт. Если хотите помочь — Pull Request и Issue приветствуются: исправление ошибок, новые функции, переводы, плагины.
|
Код полностью открыт. Если хотите помочь — Pull Request и Issue приветствуются:
|
||||||
|
исправление ошибок, новые функции, переводы.
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,51 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<component type="desktop-application">
|
<component type="desktop-application">
|
||||||
<id>io.github.vual</id>
|
<id>io.github.vual</id>
|
||||||
|
|
||||||
<name>Vual</name>
|
<name>Vual</name>
|
||||||
<summary>Launch Cheat Engine for Steam games via Proton</summary>
|
<summary>Cheat Engine launcher for Steam + Proton</summary>
|
||||||
<summary xml:lang="ru">Запуск Cheat Engine для Steam-игр через Proton</summary>
|
<summary xml:lang="ru">Запуск Cheat Engine для Steam-игр через Proton</summary>
|
||||||
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>GPL-3.0-or-later</project_license>
|
<project_license>GPL-3.0-or-later</project_license>
|
||||||
|
|
||||||
|
<developer id="io.github.cheviiot">
|
||||||
|
<name>Cheviiot</name>
|
||||||
|
</developer>
|
||||||
|
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
Vual is a GTK4/libadwaita application that lets you browse your Steam
|
Vual lets you run Cheat Engine inside any Steam game that uses Proton.
|
||||||
library, toggle protonhax launch options per game, and run Cheat Engine
|
It reads your Steam library, manages protonhax launch options, downloads
|
||||||
inside the Proton context of a running game — all from a single window.
|
and extracts Cheat Engine automatically, and launches CE in the correct
|
||||||
|
Wine prefix — all from one compact window.
|
||||||
</p>
|
</p>
|
||||||
<p xml:lang="ru">
|
<p xml:lang="ru">
|
||||||
Vual — приложение на GTK4/libadwaita, которое показывает библиотеку Steam,
|
Vual позволяет запускать Cheat Engine в любой Steam-игре, работающей
|
||||||
позволяет включать protonhax для каждой игры и запускать Cheat Engine
|
через Proton. Приложение читает библиотеку Steam, управляет launch
|
||||||
внутри Proton-контекста запущенной игры — из одного окна.
|
options для protonhax, автоматически скачивает и распаковывает Cheat
|
||||||
|
Engine и запускает CE в нужном Wine-префиксе — всё из одного окна.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>Key features:</p>
|
||||||
|
<p xml:lang="ru">Основные возможности:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Game grid with covers from Steam CDN, search, and sorting</li>
|
<li>Game grid with Steam CDN covers, real-time search, and sorting</li>
|
||||||
<li>Per-game and batch protonhax launch option management</li>
|
<li>One-click launch — game starts via Steam, CE attaches to its Proton prefix</li>
|
||||||
<li>One-click game launch with automatic Cheat Engine attach</li>
|
<li>Bind .CT cheat tables to games so they load together with CE</li>
|
||||||
<li>Download and extract Cheat Engine via Proton Wine</li>
|
<li>Per-game and bulk protonhax toggle with automatic launch option editing</li>
|
||||||
<li>Dark and light Wine theme for all Proton prefixes</li>
|
<li>Download CE from cheatengine.org and extract it through Proton Wine</li>
|
||||||
<li>Application and Cheat Engine language switching (EN/RU)</li>
|
<li>Apply dark or light Wine theme to all Proton prefixes at once</li>
|
||||||
<li xml:lang="ru">Сетка игр с обложками из Steam CDN, поиск и сортировка</li>
|
<li>Install the official Russian CE translation from the CE repository</li>
|
||||||
<li xml:lang="ru">Управление launch options protonhax по играм и массово</li>
|
<li>Built-in quick-start guide on first launch</li>
|
||||||
<li xml:lang="ru">Запуск игры с автоматическим подключением Cheat Engine</li>
|
<li xml:lang="ru">Сетка игр с обложками из Steam CDN, поиск в реальном времени, сортировка</li>
|
||||||
<li xml:lang="ru">Скачивание и распаковка Cheat Engine через Wine из Proton</li>
|
<li xml:lang="ru">Запуск одним кликом — игра стартует через Steam, CE подключается к её Proton-префиксу</li>
|
||||||
<li xml:lang="ru">Тёмная и светлая тема Wine для всех Proton-префиксов</li>
|
<li xml:lang="ru">Привязка .CT-таблиц к играм — открываются вместе с CE</li>
|
||||||
<li xml:lang="ru">Переключение языка приложения и Cheat Engine (EN/RU)</li>
|
<li xml:lang="ru">Переключатель protonhax для каждой игры и массово, автоматическая правка launch options</li>
|
||||||
|
<li xml:lang="ru">Скачивание CE с cheatengine.org и распаковка через Wine из Proton</li>
|
||||||
|
<li xml:lang="ru">Тёмная или светлая тема Wine для всех Proton-префиксов разом</li>
|
||||||
|
<li xml:lang="ru">Установка официальной русской локализации CE из репозитория CE</li>
|
||||||
|
<li xml:lang="ru">Встроенный гайд при первом запуске</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
@@ -40,9 +55,18 @@
|
|||||||
<url type="bugtracker">https://github.com/Cheviiot/vual/issues</url>
|
<url type="bugtracker">https://github.com/Cheviiot/vual/issues</url>
|
||||||
<url type="vcs-browser">https://github.com/Cheviiot/vual</url>
|
<url type="vcs-browser">https://github.com/Cheviiot/vual</url>
|
||||||
|
|
||||||
<provides>
|
<branding>
|
||||||
<binary>vual</binary>
|
<color type="primary" scheme_preference="light">#62a0ea</color>
|
||||||
</provides>
|
<color type="primary" scheme_preference="dark">#1a5fb4</color>
|
||||||
|
</branding>
|
||||||
|
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Steam game library with covers</caption>
|
||||||
|
<caption xml:lang="ru">Библиотека Steam с обложками</caption>
|
||||||
|
<image>https://raw.githubusercontent.com/Cheviiot/vual/main/data/screenshots/main.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
|
||||||
<requires>
|
<requires>
|
||||||
<display_length compare="ge">360</display_length>
|
<display_length compare="ge">360</display_length>
|
||||||
@@ -53,31 +77,44 @@
|
|||||||
<control>keyboard</control>
|
<control>keyboard</control>
|
||||||
</recommends>
|
</recommends>
|
||||||
|
|
||||||
<branding>
|
<provides>
|
||||||
<color type="primary" scheme_preference="light">#62a0ea</color>
|
<binary>vual</binary>
|
||||||
<color type="primary" scheme_preference="dark">#1a5fb4</color>
|
</provides>
|
||||||
</branding>
|
|
||||||
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<caption>Steam game library</caption>
|
|
||||||
<caption xml:lang="ru">Библиотека игр Steam</caption>
|
|
||||||
<image>https://raw.githubusercontent.com/Cheviiot/vual/main/data/screenshots/main.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.2.0" date="2026-03-19">
|
||||||
|
<description>
|
||||||
|
<p>CE table binding, Wine theme manager, built-in guide, window transparency, Adwyra-inspired theme, Russian CE locale installer.</p>
|
||||||
|
<p xml:lang="ru">Привязка CE-таблиц, менеджер тем Wine, встроенный гайд, прозрачность окна, тема Adwyra, установка русской локализации CE.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.1.0" date="2026-03-17">
|
<release version="0.1.0" date="2026-03-17">
|
||||||
<description>
|
<description>
|
||||||
<p>First release.</p>
|
<p>First release — game grid, protonhax management, CE download and launch.</p>
|
||||||
<p xml:lang="ru">Первый выпуск.</p>
|
<p xml:lang="ru">Первый выпуск — сетка игр, управление protonhax, скачивание и запуск CE.</p>
|
||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
|
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
|
|
||||||
<developer id="io.github.cheviiot">
|
<keywords>
|
||||||
<name>Cheviiot</name>
|
<keyword>cheatengine</keyword>
|
||||||
</developer>
|
<keyword>cheat engine</keyword>
|
||||||
|
<keyword>steam</keyword>
|
||||||
|
<keyword>proton</keyword>
|
||||||
|
<keyword>wine</keyword>
|
||||||
|
<keyword>games</keyword>
|
||||||
|
<keyword xml:lang="ru">чит</keyword>
|
||||||
|
<keyword xml:lang="ru">игры</keyword>
|
||||||
|
</keywords>
|
||||||
|
|
||||||
|
<categories>
|
||||||
|
<category>Game</category>
|
||||||
|
<category>Utility</category>
|
||||||
|
</categories>
|
||||||
|
|
||||||
|
<custom>
|
||||||
|
<value key="GnomeSoftware::key-colors">[(98, 160, 234)]</value>
|
||||||
|
</custom>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: vual 0.1.0\n"
|
"Project-Id-Version: vual 0.2.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
||||||
"PO-Revision-Date: 2026-03-19 00:00+0000\n"
|
"PO-Revision-Date: 2026-03-19 00:00+0000\n"
|
||||||
@@ -106,6 +106,31 @@ msgstr "Theme applied to %d prefixes"
|
|||||||
msgid "Error applying to %d prefixes"
|
msgid "Error applying to %d prefixes"
|
||||||
msgstr "Error applying to %d prefixes"
|
msgstr "Error applying to %d prefixes"
|
||||||
|
|
||||||
|
# Visual Effects
|
||||||
|
msgid "Visual Effects"
|
||||||
|
msgstr "Visual Effects"
|
||||||
|
|
||||||
|
msgid "Shadows, transparency, and accent colors"
|
||||||
|
msgstr "Shadows, transparency, and accent colors"
|
||||||
|
|
||||||
|
msgid "Shadows"
|
||||||
|
msgstr "Shadows"
|
||||||
|
|
||||||
|
msgid "Enhanced tile shadows and hover lift"
|
||||||
|
msgstr "Enhanced tile shadows and hover lift"
|
||||||
|
|
||||||
|
msgid "Transparency"
|
||||||
|
msgstr "Transparency"
|
||||||
|
|
||||||
|
msgid "Glassmorphism effect on overlays"
|
||||||
|
msgstr "Glassmorphism effect on overlays"
|
||||||
|
|
||||||
|
msgid "Accent glow"
|
||||||
|
msgstr "Accent glow"
|
||||||
|
|
||||||
|
msgid "Glow effect on active elements"
|
||||||
|
msgstr "Glow effect on active elements"
|
||||||
|
|
||||||
# Steam page
|
# Steam page
|
||||||
msgid "Steam"
|
msgid "Steam"
|
||||||
msgstr "Steam"
|
msgstr "Steam"
|
||||||
@@ -226,6 +251,18 @@ msgstr "Launch game"
|
|||||||
msgid "Launch Cheat Engine"
|
msgid "Launch Cheat Engine"
|
||||||
msgstr "Launch Cheat Engine"
|
msgstr "Launch Cheat Engine"
|
||||||
|
|
||||||
|
msgid "Assign table…"
|
||||||
|
msgstr "Assign table…"
|
||||||
|
|
||||||
|
msgid "Remove table"
|
||||||
|
msgstr "Remove table"
|
||||||
|
|
||||||
|
msgid "Select Cheat Engine table"
|
||||||
|
msgstr "Select Cheat Engine table"
|
||||||
|
|
||||||
|
msgid "CE tables (*.CT)"
|
||||||
|
msgstr "CE tables (*.CT)"
|
||||||
|
|
||||||
msgid "Launching %s..."
|
msgid "Launching %s..."
|
||||||
msgstr "Launching %s..."
|
msgstr "Launching %s..."
|
||||||
|
|
||||||
@@ -299,6 +336,91 @@ msgstr "Settings"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "About"
|
msgstr "About"
|
||||||
|
|
||||||
|
# Guide
|
||||||
|
msgid "Guide"
|
||||||
|
msgstr "Guide"
|
||||||
|
|
||||||
|
msgid "Close guide"
|
||||||
|
msgstr "Close guide"
|
||||||
|
|
||||||
|
msgid "Welcome to Vual"
|
||||||
|
msgstr "Welcome to Vual"
|
||||||
|
|
||||||
|
msgid "Getting Started"
|
||||||
|
msgstr "Getting Started"
|
||||||
|
|
||||||
|
msgid "Set up Vual in a couple of minutes"
|
||||||
|
msgstr "Set up Vual in a couple of minutes"
|
||||||
|
|
||||||
|
msgid "Vual links Steam, Proton and Cheat Engine so you can launch the game first and open CE in the right prefix without manual setup every time."
|
||||||
|
msgstr "Vual links Steam, Proton and Cheat Engine so you can launch the game first and open CE in the right prefix without manual setup every time."
|
||||||
|
|
||||||
|
msgid "Before you start"
|
||||||
|
msgstr "Before you start"
|
||||||
|
|
||||||
|
msgid "Make sure the base tools are ready before enabling games."
|
||||||
|
msgstr "Make sure the base tools are ready before enabling games."
|
||||||
|
|
||||||
|
msgid "Install or download Cheat Engine in Settings"
|
||||||
|
msgstr "Install or download Cheat Engine in Settings"
|
||||||
|
|
||||||
|
msgid "Check that the Steam path points to the correct library"
|
||||||
|
msgstr "Check that the Steam path points to the correct library"
|
||||||
|
|
||||||
|
msgid "Close Steam before changing launch options for many games"
|
||||||
|
msgstr "Close Steam before changing launch options for many games"
|
||||||
|
|
||||||
|
msgid "How Vual works"
|
||||||
|
msgstr "How Vual works"
|
||||||
|
|
||||||
|
msgid "The usual workflow is short and always the same."
|
||||||
|
msgstr "The usual workflow is short and always the same."
|
||||||
|
|
||||||
|
msgid "Pick a game"
|
||||||
|
msgstr "Pick a game"
|
||||||
|
|
||||||
|
msgid "Find the game in the library and enable the switch on its tile."
|
||||||
|
msgstr "Find the game in the library and enable the switch on its tile."
|
||||||
|
|
||||||
|
msgid "Start it from Steam"
|
||||||
|
msgstr "Start it from Steam"
|
||||||
|
|
||||||
|
msgid "Steam launches the game with protonhax and prepares the correct environment."
|
||||||
|
msgstr "Steam launches the game with protonhax and prepares the correct environment."
|
||||||
|
|
||||||
|
msgid "Open Cheat Engine"
|
||||||
|
msgstr "Open Cheat Engine"
|
||||||
|
|
||||||
|
msgid "Use the tile menu to launch CE directly for that running game."
|
||||||
|
msgstr "Use the tile menu to launch CE directly for that running game."
|
||||||
|
|
||||||
|
msgid "Optional: attach a table"
|
||||||
|
msgstr "Optional: attach a table"
|
||||||
|
|
||||||
|
msgid "Bind a .CT file once and Vual will pass it to Cheat Engine automatically."
|
||||||
|
msgstr "Bind a .CT file once and Vual will pass it to Cheat Engine automatically."
|
||||||
|
|
||||||
|
msgid "Good to know"
|
||||||
|
msgstr "Good to know"
|
||||||
|
|
||||||
|
msgid "A few things save time once the basics are configured."
|
||||||
|
msgstr "A few things save time once the basics are configured."
|
||||||
|
|
||||||
|
msgid "Use search in the header to filter large libraries quickly"
|
||||||
|
msgstr "Use search in the header to filter large libraries quickly"
|
||||||
|
|
||||||
|
msgid "The menu button on each tile contains CE actions and table binding"
|
||||||
|
msgstr "The menu button on each tile contains CE actions and table binding"
|
||||||
|
|
||||||
|
msgid "Window transparency can be toggled in Appearance settings"
|
||||||
|
msgstr "Window transparency can be toggled in Appearance settings"
|
||||||
|
|
||||||
|
msgid "Open Settings"
|
||||||
|
msgstr "Open Settings"
|
||||||
|
|
||||||
|
msgid "Open Library"
|
||||||
|
msgstr "Open Library"
|
||||||
|
|
||||||
# CE page additional
|
# CE page additional
|
||||||
msgid "All files"
|
msgid "All files"
|
||||||
msgstr "All files"
|
msgstr "All files"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: vual 0.1.0\n"
|
"Project-Id-Version: vual 0.2.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
||||||
"PO-Revision-Date: 2026-03-19 00:00+0000\n"
|
"PO-Revision-Date: 2026-03-19 00:00+0000\n"
|
||||||
@@ -54,6 +54,12 @@ msgstr "Язык интерфейса приложения"
|
|||||||
msgid "Restart app to apply language"
|
msgid "Restart app to apply language"
|
||||||
msgstr "Перезапустите приложение для смены языка"
|
msgstr "Перезапустите приложение для смены языка"
|
||||||
|
|
||||||
|
msgid "Transparent window"
|
||||||
|
msgstr "Прозрачность окна"
|
||||||
|
|
||||||
|
msgid "Semi-transparent background"
|
||||||
|
msgstr "Полупрозрачный фон"
|
||||||
|
|
||||||
msgid "Grid"
|
msgid "Grid"
|
||||||
msgstr "Сетка"
|
msgstr "Сетка"
|
||||||
|
|
||||||
@@ -106,6 +112,31 @@ msgstr "Тема применена к %d префиксам"
|
|||||||
msgid "Error applying to %d prefixes"
|
msgid "Error applying to %d prefixes"
|
||||||
msgstr "Ошибка применения к %d префиксам"
|
msgstr "Ошибка применения к %d префиксам"
|
||||||
|
|
||||||
|
# Visual Effects
|
||||||
|
msgid "Visual Effects"
|
||||||
|
msgstr "Визуальные эффекты"
|
||||||
|
|
||||||
|
msgid "Shadows, transparency, and accent colors"
|
||||||
|
msgstr "Тени, прозрачность и акцентные цвета"
|
||||||
|
|
||||||
|
msgid "Shadows"
|
||||||
|
msgstr "Тени"
|
||||||
|
|
||||||
|
msgid "Enhanced tile shadows and hover lift"
|
||||||
|
msgstr "Усиленные тени и подъём при наведении"
|
||||||
|
|
||||||
|
msgid "Transparency"
|
||||||
|
msgstr "Прозрачность"
|
||||||
|
|
||||||
|
msgid "Glassmorphism effect on overlays"
|
||||||
|
msgstr "Эффект стекла на накладках"
|
||||||
|
|
||||||
|
msgid "Accent glow"
|
||||||
|
msgstr "Акцентное свечение"
|
||||||
|
|
||||||
|
msgid "Glow effect on active elements"
|
||||||
|
msgstr "Свечение активных элементов"
|
||||||
|
|
||||||
# Steam page
|
# Steam page
|
||||||
msgid "Steam"
|
msgid "Steam"
|
||||||
msgstr "Steam"
|
msgstr "Steam"
|
||||||
@@ -226,6 +257,18 @@ msgstr "Запустить игру"
|
|||||||
msgid "Launch Cheat Engine"
|
msgid "Launch Cheat Engine"
|
||||||
msgstr "Запустить Cheat Engine"
|
msgstr "Запустить Cheat Engine"
|
||||||
|
|
||||||
|
msgid "Assign table…"
|
||||||
|
msgstr "Назначить таблицу…"
|
||||||
|
|
||||||
|
msgid "Remove table"
|
||||||
|
msgstr "Убрать таблицу"
|
||||||
|
|
||||||
|
msgid "Select Cheat Engine table"
|
||||||
|
msgstr "Выберите таблицу Cheat Engine"
|
||||||
|
|
||||||
|
msgid "CE tables (*.CT)"
|
||||||
|
msgstr "Таблицы CE (*.CT)"
|
||||||
|
|
||||||
msgid "Launching %s..."
|
msgid "Launching %s..."
|
||||||
msgstr "Запуск %s…"
|
msgstr "Запуск %s…"
|
||||||
|
|
||||||
@@ -299,6 +342,91 @@ msgstr "Настройки"
|
|||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "О программе"
|
msgstr "О программе"
|
||||||
|
|
||||||
|
# Guide
|
||||||
|
msgid "Guide"
|
||||||
|
msgstr "Гайд"
|
||||||
|
|
||||||
|
msgid "Close guide"
|
||||||
|
msgstr "Закрыть гайд"
|
||||||
|
|
||||||
|
msgid "Welcome to Vual"
|
||||||
|
msgstr "Добро пожаловать в Vual"
|
||||||
|
|
||||||
|
msgid "Getting Started"
|
||||||
|
msgstr "Быстрый старт"
|
||||||
|
|
||||||
|
msgid "Set up Vual in a couple of minutes"
|
||||||
|
msgstr "Настройте Vual за пару минут"
|
||||||
|
|
||||||
|
msgid "Vual links Steam, Proton and Cheat Engine so you can launch the game first and open CE in the right prefix without manual setup every time."
|
||||||
|
msgstr "Vual связывает Steam, Proton и Cheat Engine, чтобы вы могли сначала запускать игру, а затем открывать CE в нужном префиксе без ручной настройки каждый раз."
|
||||||
|
|
||||||
|
msgid "Before you start"
|
||||||
|
msgstr "Перед началом"
|
||||||
|
|
||||||
|
msgid "Make sure the base tools are ready before enabling games."
|
||||||
|
msgstr "Убедитесь, что базовые инструменты готовы, прежде чем включать игры."
|
||||||
|
|
||||||
|
msgid "Install or download Cheat Engine in Settings"
|
||||||
|
msgstr "Установите или скачайте Cheat Engine в настройках"
|
||||||
|
|
||||||
|
msgid "Check that the Steam path points to the correct library"
|
||||||
|
msgstr "Проверьте, что путь Steam указывает на правильную библиотеку"
|
||||||
|
|
||||||
|
msgid "Close Steam before changing launch options for many games"
|
||||||
|
msgstr "Закройте Steam перед изменением параметров запуска для множества игр"
|
||||||
|
|
||||||
|
msgid "How Vual works"
|
||||||
|
msgstr "Как работает Vual"
|
||||||
|
|
||||||
|
msgid "The usual workflow is short and always the same."
|
||||||
|
msgstr "Обычный сценарий короткий и всегда одинаковый."
|
||||||
|
|
||||||
|
msgid "Pick a game"
|
||||||
|
msgstr "Выберите игру"
|
||||||
|
|
||||||
|
msgid "Find the game in the library and enable the switch on its tile."
|
||||||
|
msgstr "Найдите игру в библиотеке и включите переключатель на её плитке."
|
||||||
|
|
||||||
|
msgid "Start it from Steam"
|
||||||
|
msgstr "Запустите её из Steam"
|
||||||
|
|
||||||
|
msgid "Steam launches the game with protonhax and prepares the correct environment."
|
||||||
|
msgstr "Steam запустит игру через protonhax и подготовит правильное окружение."
|
||||||
|
|
||||||
|
msgid "Open Cheat Engine"
|
||||||
|
msgstr "Откройте Cheat Engine"
|
||||||
|
|
||||||
|
msgid "Use the tile menu to launch CE directly for that running game."
|
||||||
|
msgstr "Используйте меню плитки, чтобы запустить CE прямо для этой запущенной игры."
|
||||||
|
|
||||||
|
msgid "Optional: attach a table"
|
||||||
|
msgstr "Необязательно: привяжите таблицу"
|
||||||
|
|
||||||
|
msgid "Bind a .CT file once and Vual will pass it to Cheat Engine automatically."
|
||||||
|
msgstr "Привяжите .CT файл один раз, и Vual будет автоматически передавать его в Cheat Engine."
|
||||||
|
|
||||||
|
msgid "Good to know"
|
||||||
|
msgstr "Полезно знать"
|
||||||
|
|
||||||
|
msgid "A few things save time once the basics are configured."
|
||||||
|
msgstr "Несколько вещей заметно экономят время после базовой настройки."
|
||||||
|
|
||||||
|
msgid "Use search in the header to filter large libraries quickly"
|
||||||
|
msgstr "Используйте поиск в заголовке, чтобы быстро фильтровать большие библиотеки"
|
||||||
|
|
||||||
|
msgid "The menu button on each tile contains CE actions and table binding"
|
||||||
|
msgstr "Кнопка меню на каждой плитке содержит действия CE и привязку таблиц"
|
||||||
|
|
||||||
|
msgid "Window transparency can be toggled in Appearance settings"
|
||||||
|
msgstr "Прозрачность окна можно включить или выключить в настройках внешнего вида"
|
||||||
|
|
||||||
|
msgid "Open Settings"
|
||||||
|
msgstr "Открыть настройки"
|
||||||
|
|
||||||
|
msgid "Open Library"
|
||||||
|
msgstr "Открыть библиотеку"
|
||||||
|
|
||||||
# CE page additional
|
# CE page additional
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr "Действия"
|
msgstr "Действия"
|
||||||
|
|||||||
+38
-1
@@ -6,7 +6,7 @@
|
|||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: vual 0.1.0\n"
|
"Project-Id-Version: vual 0.2.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
"POT-Creation-Date: 2026-03-19 00:00+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
@@ -101,6 +101,31 @@ msgstr ""
|
|||||||
msgid "Error applying to %d prefixes"
|
msgid "Error applying to %d prefixes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
# Visual Effects
|
||||||
|
msgid "Visual Effects"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Shadows, transparency, and accent colors"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Shadows"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enhanced tile shadows and hover lift"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Transparency"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Glassmorphism effect on overlays"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Accent glow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Glow effect on active elements"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Steam page
|
# Steam page
|
||||||
msgid "Steam"
|
msgid "Steam"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -224,6 +249,18 @@ msgstr ""
|
|||||||
msgid "Launch Cheat Engine"
|
msgid "Launch Cheat Engine"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Assign table…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Remove table"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Select Cheat Engine table"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CE tables (*.CT)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Launching %s..."
|
msgid "Launching %s..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "vual"
|
name = "vual"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Launch Cheat Engine for Steam games via Proton"
|
description = "Launch Cheat Engine for Steam games via Proton"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.2.0"
|
||||||
APP_ID = "io.github.vual"
|
APP_ID = "io.github.vual"
|
||||||
APP_NAME = "Vual"
|
APP_NAME = "Vual"
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import sys
|
|||||||
def _ensure_gi_available():
|
def _ensure_gi_available():
|
||||||
"""Add system site-packages to path if gi is not importable (e.g., in venv)."""
|
"""Add system site-packages to path if gi is not importable (e.g., in venv)."""
|
||||||
try:
|
try:
|
||||||
import gi # noqa: F401
|
__import__("gi")
|
||||||
return
|
return
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class VualApp(Adw.Application):
|
|||||||
win.set_default_size(self.config.window_width, self.config.window_height)
|
win.set_default_size(self.config.window_width, self.config.window_height)
|
||||||
win.present()
|
win.present()
|
||||||
|
|
||||||
|
# Show guide on first run
|
||||||
|
if not self.config.guide_shown:
|
||||||
|
win.show_guide()
|
||||||
|
|
||||||
# ── Startup: register actions ────────────────────────────────
|
# ── Startup: register actions ────────────────────────────────
|
||||||
|
|
||||||
def do_startup(self) -> None:
|
def do_startup(self) -> None:
|
||||||
@@ -67,6 +71,10 @@ class VualApp(Adw.Application):
|
|||||||
prefs_action.connect("activate", self._on_preferences)
|
prefs_action.connect("activate", self._on_preferences)
|
||||||
self.add_action(prefs_action)
|
self.add_action(prefs_action)
|
||||||
|
|
||||||
|
guide_action = Gio.SimpleAction.new("guide", None)
|
||||||
|
guide_action.connect("activate", self._on_guide)
|
||||||
|
self.add_action(guide_action)
|
||||||
|
|
||||||
quit_action = Gio.SimpleAction.new("quit", None)
|
quit_action = Gio.SimpleAction.new("quit", None)
|
||||||
quit_action.connect("activate", lambda *_: self.quit())
|
quit_action.connect("activate", lambda *_: self.quit())
|
||||||
self.add_action(quit_action)
|
self.add_action(quit_action)
|
||||||
@@ -97,3 +105,9 @@ class VualApp(Adw.Application):
|
|||||||
|
|
||||||
win = PreferencesWindow(config=self.config, transient_for=self.props.active_window)
|
win = PreferencesWindow(config=self.config, transient_for=self.props.active_window)
|
||||||
win.present()
|
win.present()
|
||||||
|
|
||||||
|
# ── Guide ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _on_guide(self, _action: Gio.SimpleAction, _param: None) -> None:
|
||||||
|
if self.props.active_window:
|
||||||
|
self.props.active_window.show_guide()
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class Config:
|
|||||||
ce_language: CE language ("system", "ru_RU").
|
ce_language: CE language ("system", "ru_RU").
|
||||||
wine_theme: Wine color theme ("system", "dark", "light").
|
wine_theme: Wine color theme ("system", "dark", "light").
|
||||||
app_language: Application UI language ("system", "en", "ru").
|
app_language: Application UI language ("system", "en", "ru").
|
||||||
|
transparent_window: Enable semi-transparent window background.
|
||||||
|
guide_shown: Whether the startup guide has been shown.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ce_executable: str = "~/.local/share/vual/cheatengine/cheatengine-x86_64.exe"
|
ce_executable: str = "~/.local/share/vual/cheatengine/cheatengine-x86_64.exe"
|
||||||
@@ -52,6 +54,8 @@ class Config:
|
|||||||
ce_language: str = "system"
|
ce_language: str = "system"
|
||||||
wine_theme: str = "system"
|
wine_theme: str = "system"
|
||||||
app_language: str = "system"
|
app_language: str = "system"
|
||||||
|
transparent_window: bool = False
|
||||||
|
guide_shown: bool = False
|
||||||
|
|
||||||
# ════════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════════
|
||||||
# Load / Save
|
# Load / Save
|
||||||
@@ -81,6 +85,8 @@ class Config:
|
|||||||
ce_language=data.get("ce_language", cls.ce_language),
|
ce_language=data.get("ce_language", cls.ce_language),
|
||||||
wine_theme=data.get("wine_theme", cls.wine_theme),
|
wine_theme=data.get("wine_theme", cls.wine_theme),
|
||||||
app_language=data.get("app_language", cls.app_language),
|
app_language=data.get("app_language", cls.app_language),
|
||||||
|
transparent_window=data.get("transparent_window", cls.transparent_window),
|
||||||
|
guide_shown=data.get("guide_shown", cls.guide_shown),
|
||||||
)
|
)
|
||||||
except (json.JSONDecodeError, OSError):
|
except (json.JSONDecodeError, OSError):
|
||||||
pass
|
pass
|
||||||
@@ -107,6 +113,8 @@ class Config:
|
|||||||
"ce_language": self.ce_language,
|
"ce_language": self.ce_language,
|
||||||
"wine_theme": self.wine_theme,
|
"wine_theme": self.wine_theme,
|
||||||
"app_language": self.app_language,
|
"app_language": self.app_language,
|
||||||
|
"transparent_window": self.transparent_window,
|
||||||
|
"guide_shown": self.guide_shown,
|
||||||
}
|
}
|
||||||
CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
return CONFIG_PATH
|
return CONFIG_PATH
|
||||||
|
|||||||
+260
-70
@@ -1,52 +1,69 @@
|
|||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
Vual — Unified Size System
|
Vual — Adwyra-inspired theme
|
||||||
|
Minimalist, clean design with soft shadows and accent highlights
|
||||||
Base unit: 4px
|
|
||||||
All sizes are multiples of 4px for consistency
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
/* ── Transparent Window ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
window.transparent-window {
|
||||||
HEADER BAR
|
background-color: alpha(@window_bg_color, 0.82);
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
}
|
||||||
|
/* ── Header Bar ───────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/* Header buttons: 32x32 (8 units) */
|
|
||||||
.header-btn {
|
.header-btn {
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 6px;
|
border-radius: 10px;
|
||||||
|
transition: background-color 150ms ease-out, box-shadow 150ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-btn:hover {
|
.header-btn:hover {
|
||||||
background: alpha(@accent_bg_color, 0.12);
|
background-color: alpha(@accent_color, 0.08);
|
||||||
|
box-shadow: 0 2px 6px alpha(black, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-btn:active {
|
.header-btn:active {
|
||||||
background: alpha(@accent_bg_color, 0.2);
|
background-color: alpha(@accent_color, 0.16);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Games counter */
|
|
||||||
.games-counter {
|
.games-counter {
|
||||||
font-size: 0.85em;
|
font-size: 0.84em;
|
||||||
font-weight: 500;
|
font-weight: 450;
|
||||||
opacity: 0.6;
|
opacity: 0.65;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search entry */
|
/* ── Search Entry ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
searchentry {
|
searchentry {
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 1px 10px;
|
||||||
|
background-color: alpha(@view_bg_color, 0.3);
|
||||||
|
border: 1px solid alpha(@borders, 0.1);
|
||||||
|
box-shadow: 0 1px 3px alpha(black, 0.04);
|
||||||
|
transition: box-shadow 200ms ease-out, background-color 200ms ease-out,
|
||||||
|
border-color 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
searchentry:focus {
|
||||||
GRID LAYOUT
|
background-color: @view_bg_color;
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
border-color: alpha(@accent_color, 0.35);
|
||||||
|
box-shadow: 0 0 0 3px alpha(@accent_color, 0.15);
|
||||||
.vual-grid {
|
|
||||||
/* Padding handled in Python via set_margin_* */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchentry image {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchentry:focus image {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Grid Layout ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
flowboxchild {
|
flowboxchild {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -64,49 +81,47 @@ flowboxchild:selected {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ── Tile ─────────────────────────────────────────────────────────────────── */
|
||||||
TILE
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
/* Tile container: 12px radius */
|
|
||||||
.vual-tile {
|
.vual-tile {
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
background: @card_bg_color;
|
background: @card_bg_color;
|
||||||
box-shadow: 0 1px 2px alpha(black, 0.08);
|
box-shadow: 0 1px 3px alpha(black, 0.10), 0 0 0 1px alpha(black, 0.04);
|
||||||
|
transition: box-shadow 150ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-tile:hover {
|
.vual-tile:hover {
|
||||||
box-shadow: 0 2px 8px alpha(black, 0.15);
|
box-shadow: 0 4px 12px alpha(black, 0.14), 0 0 0 1px alpha(black, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ── Tile Info Overlay ────────────────────────────────────────────────────── */
|
||||||
TILE INFO OVERLAY (bottom)
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
/* Info panel: gradient background */
|
|
||||||
.vual-tile-info {
|
.vual-tile-info {
|
||||||
padding: 24px 8px 8px 8px;
|
padding: 20px 8px 8px 8px;
|
||||||
background: linear-gradient(to bottom,
|
background: linear-gradient(to bottom,
|
||||||
transparent 0%,
|
transparent 0%,
|
||||||
alpha(black, 0.6) 50%,
|
alpha(black, 0.55) 50%,
|
||||||
alpha(black, 0.8) 100%);
|
alpha(black, 0.75) 100%);
|
||||||
border-radius: 0 0 12px 12px;
|
border-radius: 0 0 14px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-tile-info label {
|
.vual-tile-info label {
|
||||||
color: white;
|
color: white;
|
||||||
|
transition: opacity 150ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-tile-info .heading {
|
.vual-tile-info .heading {
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
font-size: 0.85em;
|
font-size: 0.84em;
|
||||||
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
.vual-tile:hover .vual-tile-info .heading {
|
||||||
TILE BUTTONS (play, CE)
|
opacity: 1;
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
}
|
||||||
|
|
||||||
|
/* ── Tile Buttons ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/* Tile buttons: 28x28 (7 units) */
|
|
||||||
.tile-btn,
|
.tile-btn,
|
||||||
.vual-tile-info button {
|
.vual-tile-info button {
|
||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
@@ -114,28 +129,47 @@ flowboxchild:selected {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: white;
|
color: white;
|
||||||
background: alpha(white, 0.12);
|
background: alpha(white, 0.08);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
|
transition: background-color 150ms ease-out, box-shadow 150ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-btn:hover,
|
.tile-btn:hover,
|
||||||
.vual-tile-info button:hover {
|
.vual-tile-info button:hover {
|
||||||
background: alpha(white, 0.24);
|
background: alpha(white, 0.18);
|
||||||
|
box-shadow: 0 2px 6px alpha(black, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-btn:active,
|
.tile-btn:active,
|
||||||
.vual-tile-info button:active {
|
.vual-tile-info button:active {
|
||||||
background: alpha(white, 0.32);
|
background: alpha(white, 0.26);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* Popover menus — compact */
|
||||||
TILE SWITCH
|
popover contents {
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
popover modelbutton {
|
||||||
|
min-height: 22px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.88em;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background-color 100ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
popover modelbutton:hover {
|
||||||
|
background-color: alpha(@accent_color, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Tile Switch ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.vual-tile-info switch {
|
.vual-tile-info switch {
|
||||||
background: alpha(white, 0.15);
|
background: alpha(white, 0.12);
|
||||||
border: none;
|
border: none;
|
||||||
|
transition: background-color 150ms ease-out, box-shadow 150ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-tile-info switch:checked {
|
.vual-tile-info switch:checked {
|
||||||
@@ -146,50 +180,206 @@ flowboxchild:selected {
|
|||||||
background: white;
|
background: white;
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
min-height: 16px;
|
min-height: 16px;
|
||||||
border-radius: 8px;
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 1px 3px alpha(black, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ── Reload Button ────────────────────────────────────────────────────────── */
|
||||||
RELOAD BUTTON (top-left corner)
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
/* Reload: 24x24 (6 units) */
|
|
||||||
.vual-reload-btn {
|
.vual-reload-btn {
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 6px;
|
border-radius: 50%;
|
||||||
background: alpha(black, 0.5);
|
background: alpha(black, 0.35);
|
||||||
color: white;
|
color: white;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
border: none;
|
border: none;
|
||||||
|
transition: opacity 200ms ease-out, background-color 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-tile:hover .vual-reload-btn {
|
.vual-tile:hover .vual-reload-btn {
|
||||||
opacity: 1;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vual-reload-btn:hover {
|
.vual-reload-btn:hover {
|
||||||
background: @accent_bg_color;
|
opacity: 1;
|
||||||
|
background: alpha(@accent_color, 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ── Running Badge ────────────────────────────────────────────────────────── */
|
||||||
RUNNING BADGE (top-right corner)
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
/* Badge: 20px total (14px icon + 3px padding each side) */
|
|
||||||
.vual-badge {
|
.vual-badge {
|
||||||
background: @success_bg_color;
|
min-width: 12px;
|
||||||
|
min-height: 12px;
|
||||||
|
background: radial-gradient(circle at 35% 35%, alpha(white, 0.95) 0%, alpha(@accent_color, 0.95) 32%, @accent_color 68%, alpha(@accent_color, 0.88) 100%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 3px;
|
border: 1px solid alpha(white, 0.45);
|
||||||
|
box-shadow: 0 0 0 1px alpha(@accent_color, 0.16), 0 0 8px alpha(@accent_color, 0.55), 0 0 16px alpha(@accent_color, 0.32), 0 2px 6px alpha(black, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ── Skeleton ─────────────────────────────────────────────────────────────── */
|
||||||
SKELETON (loading placeholder)
|
|
||||||
═══════════════════════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
background: alpha(@card_shade_color, 0.2);
|
background: alpha(@card_shade_color, 0.15);
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
GUIDE PAGE
|
||||||
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
.guide-page {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-hero {
|
||||||
|
padding: 24px 24px 28px 24px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
alpha(@accent_color, 0.12) 0%,
|
||||||
|
alpha(@accent_color, 0.04) 100%
|
||||||
|
);
|
||||||
|
border: 1px solid alpha(@accent_color, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-eyebrow {
|
||||||
|
font-size: 0.82em;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-title {
|
||||||
|
font-size: 2.1em;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-subtitle {
|
||||||
|
font-size: 1.02em;
|
||||||
|
opacity: 0.78;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-card {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: alpha(@view_bg_color, 0.55);
|
||||||
|
border: 1px solid alpha(@borders, 0.12);
|
||||||
|
box-shadow: 0 1px 3px alpha(black, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-card-title {
|
||||||
|
font-size: 1.08em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-card-subtitle {
|
||||||
|
opacity: 0.7;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-list-row {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-bullet {
|
||||||
|
min-width: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-list-text {
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step-badge {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: alpha(@accent_color, 0.14);
|
||||||
|
color: @accent_color;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step-title {
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step-description {
|
||||||
|
opacity: 0.72;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
PREFERENCES
|
||||||
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
preferencesgroup > box > .header {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
preferencesgroup > box > box.header > box.title > label.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.84em;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
opacity: 0.55;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
preferencesgroup > box > .header > label.description {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Boxed lists */
|
||||||
|
.boxed-list {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
border: 1px solid alpha(@borders, 0.12);
|
||||||
|
background-color: alpha(@view_bg_color, 0.45);
|
||||||
|
box-shadow: 0 1px 3px alpha(black, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
row {
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 40px;
|
||||||
|
transition: background-color 120ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
row:hover {
|
||||||
|
background-color: alpha(@accent_color, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
UTILITY CLASSES
|
||||||
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
.dimmed {
|
||||||
|
opacity: 0.45;
|
||||||
|
transition: opacity 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimmed:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success { color: @success_color; }
|
||||||
|
.warning { color: @warning_color; }
|
||||||
|
|
||||||
|
separator {
|
||||||
|
background-color: alpha(@borders, 0.18);
|
||||||
|
margin: 4px 0;
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-19
@@ -23,7 +23,6 @@ _LOCALE_DIRS = [
|
|||||||
|
|
||||||
# Global translator
|
# Global translator
|
||||||
_translator: gettext.GNUTranslations | gettext.NullTranslations | None = None
|
_translator: gettext.GNUTranslations | gettext.NullTranslations | None = None
|
||||||
_current_lang: str = "system"
|
|
||||||
|
|
||||||
|
|
||||||
def _find_locale_dir() -> Path | None:
|
def _find_locale_dir() -> Path | None:
|
||||||
@@ -47,8 +46,7 @@ def init(lang: str = "system") -> None:
|
|||||||
lang: Language code ("system", "en", "ru").
|
lang: Language code ("system", "en", "ru").
|
||||||
"system" uses system locale.
|
"system" uses system locale.
|
||||||
"""
|
"""
|
||||||
global _translator, _current_lang
|
global _translator
|
||||||
_current_lang = lang
|
|
||||||
|
|
||||||
# Get system locale
|
# Get system locale
|
||||||
try:
|
try:
|
||||||
@@ -78,23 +76,8 @@ def init(lang: str = "system") -> None:
|
|||||||
_translator = gettext.NullTranslations()
|
_translator = gettext.NullTranslations()
|
||||||
|
|
||||||
|
|
||||||
def set_language(lang: str) -> None:
|
|
||||||
"""Change application language.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
lang: Language code ("system", "en", "ru").
|
|
||||||
"""
|
|
||||||
init(lang)
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_language() -> str:
|
|
||||||
"""Get current language setting."""
|
|
||||||
return _current_lang
|
|
||||||
|
|
||||||
|
|
||||||
def _(message: str) -> str:
|
def _(message: str) -> str:
|
||||||
"""Translate a message string."""
|
"""Translate a message string."""
|
||||||
global _translator
|
|
||||||
if _translator is None:
|
if _translator is None:
|
||||||
init()
|
init()
|
||||||
return _translator.gettext(message) if _translator else message
|
return _translator.gettext(message) if _translator else message
|
||||||
@@ -102,7 +85,6 @@ def _(message: str) -> str:
|
|||||||
|
|
||||||
def ngettext(singular: str, plural: str, n: int) -> str:
|
def ngettext(singular: str, plural: str, n: int) -> str:
|
||||||
"""Translate a plural message."""
|
"""Translate a plural message."""
|
||||||
global _translator
|
|
||||||
if _translator is None:
|
if _translator is None:
|
||||||
init()
|
init()
|
||||||
return _translator.ngettext(singular, plural, n) if _translator else (singular if n == 1 else plural)
|
return _translator.ngettext(singular, plural, n) if _translator else (singular if n == 1 else plural)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,76 @@
|
|||||||
|
"""Cheat Engine table (.CT) storage and per-game binding.
|
||||||
|
|
||||||
|
Tables are stored in ~/.local/share/vual/tables/.
|
||||||
|
Bindings (app_id → filename) are persisted in tables.json alongside the files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
TABLES_DIR = Path(GLib.get_user_data_dir()) / "vual" / "tables"
|
||||||
|
_MAP_FILE = TABLES_DIR / "tables.json"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Internal helpers ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _load_map() -> dict[str, str]:
|
||||||
|
if _MAP_FILE.exists():
|
||||||
|
try:
|
||||||
|
return json.loads(_MAP_FILE.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_map(data: dict[str, str]) -> None:
|
||||||
|
TABLES_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
_MAP_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Public API ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def bind(app_id: str, src: str | Path) -> Path:
|
||||||
|
"""Copy a .CT file into the tables directory and bind it to *app_id*.
|
||||||
|
|
||||||
|
Returns the destination path inside TABLES_DIR.
|
||||||
|
"""
|
||||||
|
src = Path(src)
|
||||||
|
TABLES_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
dest = TABLES_DIR / src.name
|
||||||
|
if dest.resolve() != src.resolve():
|
||||||
|
shutil.copy2(src, dest)
|
||||||
|
|
||||||
|
data = _load_map()
|
||||||
|
data[app_id] = dest.name
|
||||||
|
_save_map(data)
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
|
def unbind(app_id: str) -> None:
|
||||||
|
"""Remove the table binding for *app_id* (file kept on disk)."""
|
||||||
|
data = _load_map()
|
||||||
|
if app_id in data:
|
||||||
|
del data[app_id]
|
||||||
|
_save_map(data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_table(app_id: str) -> Path | None:
|
||||||
|
"""Return the full path to the bound .CT file, or None."""
|
||||||
|
data = _load_map()
|
||||||
|
name = data.get(app_id)
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
path = TABLES_DIR / name
|
||||||
|
return path if path.is_file() else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_name(app_id: str) -> str | None:
|
||||||
|
"""Return just the filename of the bound table, or None."""
|
||||||
|
data = _load_map()
|
||||||
|
return data.get(app_id)
|
||||||
+102
-25
@@ -19,11 +19,11 @@ gi.require_version("Gdk", "4.0")
|
|||||||
gi.require_version("GdkPixbuf", "2.0")
|
gi.require_version("GdkPixbuf", "2.0")
|
||||||
gi.require_version("Pango", "1.0")
|
gi.require_version("Pango", "1.0")
|
||||||
|
|
||||||
from gi.repository import Adw, Gdk, GdkPixbuf, GLib, Gtk, Pango
|
from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk, Pango
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from vual import protonhax, steam
|
from vual import protonhax, steam, tables
|
||||||
from vual.config import TILE_SIZES, Config
|
from vual.config import TILE_SIZES, Config
|
||||||
from vual.i18n import _
|
from vual.i18n import _
|
||||||
|
|
||||||
@@ -217,14 +217,39 @@ class GameTile(Gtk.Overlay):
|
|||||||
btn_play.connect("clicked", self._on_play_clicked)
|
btn_play.connect("clicked", self._on_play_clicked)
|
||||||
row.append(btn_play)
|
row.append(btn_play)
|
||||||
|
|
||||||
# CE button
|
# CE button with table menu
|
||||||
btn_ce = Gtk.Button()
|
self._btn_ce = Gtk.MenuButton()
|
||||||
btn_ce.set_icon_name("utilities-terminal-symbolic")
|
self._btn_ce.set_icon_name("open-menu-symbolic")
|
||||||
btn_ce.add_css_class("flat")
|
self._btn_ce.add_css_class("flat")
|
||||||
btn_ce.add_css_class("tile-btn")
|
self._btn_ce.add_css_class("tile-btn")
|
||||||
btn_ce.set_tooltip_text(_("Launch Cheat Engine"))
|
self._update_ce_tooltip()
|
||||||
btn_ce.connect("clicked", self._on_ce_clicked)
|
|
||||||
row.append(btn_ce)
|
ce_menu = Gio.Menu()
|
||||||
|
ce_menu.append(_("Launch Cheat Engine"), "tile.launch-ce")
|
||||||
|
ce_menu.append(_("Assign table…"), "tile.assign-table")
|
||||||
|
ce_menu.append(_("Remove table"), "tile.remove-table")
|
||||||
|
|
||||||
|
pop = Gtk.PopoverMenu.new_from_model(ce_menu)
|
||||||
|
self._btn_ce.set_popover(pop)
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
group = Gio.SimpleActionGroup()
|
||||||
|
|
||||||
|
act_launch = Gio.SimpleAction.new("launch-ce", None)
|
||||||
|
act_launch.connect("activate", lambda *_: self._on_ce_clicked(None))
|
||||||
|
group.add_action(act_launch)
|
||||||
|
|
||||||
|
act_assign = Gio.SimpleAction.new("assign-table", None)
|
||||||
|
act_assign.connect("activate", lambda *_: self._on_assign_table())
|
||||||
|
group.add_action(act_assign)
|
||||||
|
|
||||||
|
self._act_remove = Gio.SimpleAction.new("remove-table", None)
|
||||||
|
self._act_remove.connect("activate", lambda *_: self._on_remove_table())
|
||||||
|
self._act_remove.set_enabled(tables.get_table(self._app_id) is not None)
|
||||||
|
group.add_action(self._act_remove)
|
||||||
|
|
||||||
|
self.insert_action_group("tile", group)
|
||||||
|
row.append(self._btn_ce)
|
||||||
|
|
||||||
# Spacer
|
# Spacer
|
||||||
spacer = Gtk.Box()
|
spacer = Gtk.Box()
|
||||||
@@ -257,8 +282,7 @@ class GameTile(Gtk.Overlay):
|
|||||||
|
|
||||||
def _build_running_badge(self) -> None:
|
def _build_running_badge(self) -> None:
|
||||||
"""Create running badge in top-right corner."""
|
"""Create running badge in top-right corner."""
|
||||||
self._badge = Gtk.Image.new_from_icon_name("media-playback-start-symbolic")
|
self._badge = Gtk.Box()
|
||||||
self._badge.set_pixel_size(14)
|
|
||||||
self._badge.add_css_class("vual-badge")
|
self._badge.add_css_class("vual-badge")
|
||||||
self._badge.set_valign(Gtk.Align.START)
|
self._badge.set_valign(Gtk.Align.START)
|
||||||
self._badge.set_halign(Gtk.Align.END)
|
self._badge.set_halign(Gtk.Align.END)
|
||||||
@@ -322,12 +346,35 @@ class GameTile(Gtk.Overlay):
|
|||||||
if hasattr(self, "_on_launch"):
|
if hasattr(self, "_on_launch"):
|
||||||
self._on_launch(self._app_id, self._name)
|
self._on_launch(self._app_id, self._name)
|
||||||
|
|
||||||
def _on_ce_clicked(self, _btn: Gtk.Button) -> None:
|
def _on_ce_clicked(self, _btn: Gtk.Button | None) -> None:
|
||||||
"""Emit CE signal."""
|
"""Emit CE signal."""
|
||||||
if hasattr(self, "_on_launch_ce"):
|
if hasattr(self, "_on_launch_ce"):
|
||||||
self._on_launch_ce(self._app_id, self._name)
|
self._on_launch_ce(self._app_id, self._name)
|
||||||
|
|
||||||
def _on_switch_toggled(self, switch: Gtk.Switch, state: bool) -> bool:
|
def _on_assign_table(self) -> None:
|
||||||
|
"""Open file chooser to assign a .CT table."""
|
||||||
|
if hasattr(self, "_on_pick_table"):
|
||||||
|
self._on_pick_table(self._app_id)
|
||||||
|
|
||||||
|
def _on_remove_table(self) -> None:
|
||||||
|
"""Remove the bound table."""
|
||||||
|
tables.unbind(self._app_id)
|
||||||
|
self._act_remove.set_enabled(False)
|
||||||
|
self._update_ce_tooltip()
|
||||||
|
|
||||||
|
def _update_ce_tooltip(self) -> None:
|
||||||
|
name = tables.get_table_name(self._app_id)
|
||||||
|
if name:
|
||||||
|
self._btn_ce.set_tooltip_text(f"CE: {name}")
|
||||||
|
else:
|
||||||
|
self._btn_ce.set_tooltip_text(_("Launch Cheat Engine"))
|
||||||
|
|
||||||
|
def mark_table_bound(self) -> None:
|
||||||
|
"""Refresh table indicator after binding."""
|
||||||
|
self._act_remove.set_enabled(tables.get_table(self._app_id) is not None)
|
||||||
|
self._update_ce_tooltip()
|
||||||
|
|
||||||
|
def _on_switch_toggled(self, _switch: Gtk.Switch, state: bool) -> bool:
|
||||||
"""Handle activation toggle."""
|
"""Handle activation toggle."""
|
||||||
self._is_active = state
|
self._is_active = state
|
||||||
if hasattr(self, "_on_toggle"):
|
if hasattr(self, "_on_toggle"):
|
||||||
@@ -353,6 +400,9 @@ class GameTile(Gtk.Overlay):
|
|||||||
def connect_reload(self, callback) -> None:
|
def connect_reload(self, callback) -> None:
|
||||||
self._on_reload = callback
|
self._on_reload = callback
|
||||||
|
|
||||||
|
def connect_pick_table(self, callback) -> None:
|
||||||
|
self._on_pick_table = callback
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# MainPage — the main games grid
|
# MainPage — the main games grid
|
||||||
@@ -473,11 +523,6 @@ class MainPage(Adw.Bin):
|
|||||||
for app_id, tile in self._tiles.items():
|
for app_id, tile in self._tiles.items():
|
||||||
tile.set_running(app_id in running_ids)
|
tile.set_running(app_id in running_ids)
|
||||||
|
|
||||||
@property
|
|
||||||
def games_count(self) -> int:
|
|
||||||
"""Number of loaded games."""
|
|
||||||
return len(self._tiles)
|
|
||||||
|
|
||||||
# ─── Filter & Sort ────────────────────────────────────────────────────────
|
# ─── Filter & Sort ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _filter_func(self, child: Gtk.FlowBoxChild) -> bool:
|
def _filter_func(self, child: Gtk.FlowBoxChild) -> bool:
|
||||||
@@ -567,6 +612,7 @@ class MainPage(Adw.Bin):
|
|||||||
tile.connect_launch_ce(self._on_launch_ce)
|
tile.connect_launch_ce(self._on_launch_ce)
|
||||||
tile.connect_toggle(self._on_toggle)
|
tile.connect_toggle(self._on_toggle)
|
||||||
tile.connect_reload(self._on_reload_cover)
|
tile.connect_reload(self._on_reload_cover)
|
||||||
|
tile.connect_pick_table(self._on_pick_table)
|
||||||
|
|
||||||
self._tiles[app_id] = tile
|
self._tiles[app_id] = tile
|
||||||
self._grid.append(tile)
|
self._grid.append(tile)
|
||||||
@@ -666,16 +712,50 @@ class MainPage(Adw.Bin):
|
|||||||
self._win.show_toast(_("CE not found: %s") % self._cfg.ce_executable)
|
self._win.show_toast(_("CE not found: %s") % self._cfg.ce_executable)
|
||||||
return
|
return
|
||||||
|
|
||||||
args = None
|
args: list[str] = []
|
||||||
if self._cfg.ce_language != "system":
|
if self._cfg.ce_language != "system":
|
||||||
args = ["--LANG", self._cfg.ce_language]
|
args.extend(["--LANG", self._cfg.ce_language])
|
||||||
|
|
||||||
proc = protonhax.run_in_proton(app_id, str(self._cfg.ce_executable_path), args)
|
ct = tables.get_table(app_id)
|
||||||
|
if ct:
|
||||||
|
args.append(str(ct))
|
||||||
|
|
||||||
|
proc = protonhax.run_in_proton(app_id, str(self._cfg.ce_executable_path), args or None)
|
||||||
if proc:
|
if proc:
|
||||||
self._win.show_toast(_("CE launched for %s") % name)
|
self._win.show_toast(_("CE launched for %s") % name)
|
||||||
else:
|
else:
|
||||||
self._win.show_toast(_("Failed to launch CE for %s") % name)
|
self._win.show_toast(_("Failed to launch CE for %s") % name)
|
||||||
|
|
||||||
|
def _on_pick_table(self, app_id: str) -> None:
|
||||||
|
"""Open file chooser to select a .CT table for *app_id*."""
|
||||||
|
dialog = Gtk.FileDialog()
|
||||||
|
dialog.set_title(_("Select Cheat Engine table"))
|
||||||
|
|
||||||
|
f = Gtk.FileFilter()
|
||||||
|
f.set_name(_("CE tables (*.CT)"))
|
||||||
|
f.add_pattern("*.CT")
|
||||||
|
f.add_pattern("*.ct")
|
||||||
|
filters = Gio.ListStore.new(Gtk.FileFilter)
|
||||||
|
filters.append(f)
|
||||||
|
dialog.set_filters(filters)
|
||||||
|
|
||||||
|
dialog.open(self._win, None, self._on_table_chosen, app_id)
|
||||||
|
|
||||||
|
def _on_table_chosen(self, dialog: Gtk.FileDialog, result: Gio.AsyncResult, app_id: str) -> None:
|
||||||
|
try:
|
||||||
|
gfile = dialog.open_finish(result)
|
||||||
|
except GLib.Error:
|
||||||
|
return
|
||||||
|
if not gfile:
|
||||||
|
return
|
||||||
|
path = gfile.get_path()
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
tables.bind(app_id, path)
|
||||||
|
tile = self._tiles.get(app_id)
|
||||||
|
if tile:
|
||||||
|
tile.mark_table_bound()
|
||||||
|
|
||||||
def _on_toggle(self, app_id: str, active: bool) -> None:
|
def _on_toggle(self, app_id: str, active: bool) -> None:
|
||||||
"""Handle activation toggle."""
|
"""Handle activation toggle."""
|
||||||
tile = self._tiles.get(app_id)
|
tile = self._tiles.get(app_id)
|
||||||
@@ -811,6 +891,3 @@ class MainPage(Adw.Bin):
|
|||||||
if restart:
|
if restart:
|
||||||
steam.start_steam()
|
steam.start_steam()
|
||||||
|
|
||||||
def schedule_auto_launch(self, app_id: str) -> None:
|
|
||||||
"""Schedule auto CE launch for a game."""
|
|
||||||
self._auto_ce = app_id
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
self._config = config
|
self._config = config
|
||||||
|
self._parent = kwargs.get("transient_for")
|
||||||
self._exclusion_rows: list[Adw.EntryRow] = []
|
self._exclusion_rows: list[Adw.EntryRow] = []
|
||||||
self._release_info: dict | None = None
|
self._release_info: dict | None = None
|
||||||
self._building = True # Prevent saves during initial build
|
self._building = True # Prevent saves during initial build
|
||||||
@@ -95,6 +96,15 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
|||||||
self._lang_app_row.connect("notify::selected", self._on_app_lang_changed)
|
self._lang_app_row.connect("notify::selected", self._on_app_lang_changed)
|
||||||
theme_group.add(self._lang_app_row)
|
theme_group.add(self._lang_app_row)
|
||||||
|
|
||||||
|
# Transparent window toggle
|
||||||
|
self._transparent_row = Adw.SwitchRow(
|
||||||
|
title=_("Transparent window"),
|
||||||
|
subtitle=_("Semi-transparent background"),
|
||||||
|
)
|
||||||
|
self._transparent_row.set_active(self._config.transparent_window)
|
||||||
|
self._transparent_row.connect("notify::active", self._on_transparent_changed)
|
||||||
|
theme_group.add(self._transparent_row)
|
||||||
|
|
||||||
# Grid group
|
# Grid group
|
||||||
grid_group = Adw.PreferencesGroup(
|
grid_group = Adw.PreferencesGroup(
|
||||||
title=_("Grid"),
|
title=_("Grid"),
|
||||||
@@ -179,6 +189,12 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
|||||||
# Show restart hint
|
# Show restart hint
|
||||||
self._show_toast(_("Restart app to apply language"))
|
self._show_toast(_("Restart app to apply language"))
|
||||||
|
|
||||||
|
def _on_transparent_changed(self, row: Adw.SwitchRow, _pspec) -> None:
|
||||||
|
self._config.transparent_window = row.get_active()
|
||||||
|
self._save()
|
||||||
|
if self._parent:
|
||||||
|
self._parent.apply_transparency()
|
||||||
|
|
||||||
def _apply_theme(self) -> None:
|
def _apply_theme(self) -> None:
|
||||||
style = Adw.StyleManager.get_default()
|
style = Adw.StyleManager.get_default()
|
||||||
schemes = {
|
schemes = {
|
||||||
|
|||||||
+207
-5
@@ -31,12 +31,23 @@ class VualWindow(Adw.ApplicationWindow):
|
|||||||
self.toast_overlay = Adw.ToastOverlay()
|
self.toast_overlay = Adw.ToastOverlay()
|
||||||
self.set_content(self.toast_overlay)
|
self.set_content(self.toast_overlay)
|
||||||
|
|
||||||
toolbar_view = Adw.ToolbarView()
|
# Navigation view for guide/main switching
|
||||||
self.toast_overlay.set_child(toolbar_view)
|
self._nav_view = Adw.NavigationView()
|
||||||
|
self.toast_overlay.set_child(self._nav_view)
|
||||||
|
|
||||||
# Single main page
|
# Main page
|
||||||
|
self._main_nav_page = self._build_main_page()
|
||||||
|
self._nav_view.add(self._main_nav_page)
|
||||||
|
|
||||||
|
# Apply transparency from config
|
||||||
|
self.apply_transparency()
|
||||||
|
|
||||||
|
def _build_main_page(self) -> Adw.NavigationPage:
|
||||||
|
"""Build the main games grid page."""
|
||||||
from vual.ui.main_page import MainPage
|
from vual.ui.main_page import MainPage
|
||||||
|
|
||||||
|
toolbar_view = Adw.ToolbarView()
|
||||||
|
|
||||||
self.main_page = MainPage(config=self.config, window=self)
|
self.main_page = MainPage(config=self.config, window=self)
|
||||||
|
|
||||||
# Header bar
|
# Header bar
|
||||||
@@ -102,7 +113,9 @@ class VualWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
toolbar_view.set_content(self.main_page)
|
toolbar_view.set_content(self.main_page)
|
||||||
|
|
||||||
def _on_sort_changed(self, action: Gio.SimpleAction, param: GLib.Variant) -> None:
|
return Adw.NavigationPage(child=toolbar_view, title=APP_NAME, tag="main")
|
||||||
|
|
||||||
|
def _on_sort_changed(self, _action: Gio.SimpleAction, param: GLib.Variant) -> None:
|
||||||
sort_by = param.get_string()
|
sort_by = param.get_string()
|
||||||
self.config.sort_by = sort_by
|
self.config.sort_by = sort_by
|
||||||
self.main_page._update_sort()
|
self.main_page._update_sort()
|
||||||
@@ -117,7 +130,7 @@ class VualWindow(Adw.ApplicationWindow):
|
|||||||
menu = Gio.Menu()
|
menu = Gio.Menu()
|
||||||
menu.append(_("Preferences"), "app.preferences")
|
menu.append(_("Preferences"), "app.preferences")
|
||||||
menu.append(_("About"), "app.about")
|
menu.append(_("About"), "app.about")
|
||||||
menu.append(_("Quit"), "app.quit")
|
menu.append(_("Guide"), "app.guide")
|
||||||
return menu
|
return menu
|
||||||
|
|
||||||
# ── Toast helper ─────────────────────────────────────────────
|
# ── Toast helper ─────────────────────────────────────────────
|
||||||
@@ -126,3 +139,192 @@ class VualWindow(Adw.ApplicationWindow):
|
|||||||
toast = Adw.Toast(title=message, timeout=timeout)
|
toast = Adw.Toast(title=message, timeout=timeout)
|
||||||
self.toast_overlay.add_toast(toast)
|
self.toast_overlay.add_toast(toast)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# ── Transparency ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
def apply_transparency(self) -> None:
|
||||||
|
"""Apply or remove transparent window CSS class."""
|
||||||
|
if self.config.transparent_window:
|
||||||
|
self.add_css_class("transparent-window")
|
||||||
|
else:
|
||||||
|
self.remove_css_class("transparent-window")
|
||||||
|
|
||||||
|
# ── Guide page ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
def show_guide(self) -> None:
|
||||||
|
"""Show fullscreen guide page."""
|
||||||
|
guide_page = self._build_guide_page()
|
||||||
|
self._nav_view.push(guide_page)
|
||||||
|
|
||||||
|
def _build_guide_page(self) -> Adw.NavigationPage:
|
||||||
|
"""Build the guide/onboarding page."""
|
||||||
|
toolbar_view = Adw.ToolbarView()
|
||||||
|
|
||||||
|
header = Adw.HeaderBar()
|
||||||
|
header.add_css_class("flat")
|
||||||
|
toolbar_view.add_top_bar(header)
|
||||||
|
|
||||||
|
scroll = Gtk.ScrolledWindow(vexpand=True)
|
||||||
|
toolbar_view.set_content(scroll)
|
||||||
|
|
||||||
|
clamp = Adw.Clamp(maximum_size=760)
|
||||||
|
scroll.set_child(clamp)
|
||||||
|
|
||||||
|
content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
|
||||||
|
content.add_css_class("guide-page")
|
||||||
|
content.set_margin_top(28)
|
||||||
|
content.set_margin_bottom(40)
|
||||||
|
content.set_margin_start(24)
|
||||||
|
content.set_margin_end(24)
|
||||||
|
clamp.set_child(content)
|
||||||
|
|
||||||
|
hero = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||||
|
hero.add_css_class("guide-hero")
|
||||||
|
content.append(hero)
|
||||||
|
|
||||||
|
eyebrow = Gtk.Label(label=_("Getting Started"), xalign=0)
|
||||||
|
eyebrow.add_css_class("guide-eyebrow")
|
||||||
|
hero.append(eyebrow)
|
||||||
|
|
||||||
|
title = Gtk.Label(label=_("Set up Vual in a couple of minutes"), xalign=0)
|
||||||
|
title.add_css_class("guide-title")
|
||||||
|
title.set_wrap(True)
|
||||||
|
hero.append(title)
|
||||||
|
|
||||||
|
subtitle = Gtk.Label(
|
||||||
|
label=_("Vual links Steam, Proton and Cheat Engine so you can launch the game first and open CE in the right prefix without manual setup every time."),
|
||||||
|
xalign=0,
|
||||||
|
wrap=True,
|
||||||
|
)
|
||||||
|
subtitle.add_css_class("guide-subtitle")
|
||||||
|
hero.append(subtitle)
|
||||||
|
|
||||||
|
prep_card = self._build_guide_card(
|
||||||
|
_("Before you start"),
|
||||||
|
_("Make sure the base tools are ready before enabling games."),
|
||||||
|
[
|
||||||
|
_("Install or download Cheat Engine in Settings"),
|
||||||
|
_("Check that the Steam path points to the correct library"),
|
||||||
|
_("Close Steam before changing launch options for many games"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
content.append(prep_card)
|
||||||
|
|
||||||
|
flow_card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=14)
|
||||||
|
flow_card.add_css_class("guide-card")
|
||||||
|
content.append(flow_card)
|
||||||
|
|
||||||
|
flow_title = Gtk.Label(label=_("How Vual works"), xalign=0)
|
||||||
|
flow_title.add_css_class("guide-card-title")
|
||||||
|
flow_card.append(flow_title)
|
||||||
|
|
||||||
|
flow_subtitle = Gtk.Label(
|
||||||
|
label=_("The usual workflow is short and always the same."),
|
||||||
|
xalign=0,
|
||||||
|
wrap=True,
|
||||||
|
)
|
||||||
|
flow_subtitle.add_css_class("guide-card-subtitle")
|
||||||
|
flow_card.append(flow_subtitle)
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
(_("Pick a game"), _("Find the game in the library and enable the switch on its tile.")),
|
||||||
|
(_("Start it from Steam"), _("Steam launches the game with protonhax and prepares the correct environment.")),
|
||||||
|
(_("Open Cheat Engine"), _("Use the tile menu to launch CE directly for that running game.")),
|
||||||
|
(_("Optional: attach a table"), _("Bind a .CT file once and Vual will pass it to Cheat Engine automatically.")),
|
||||||
|
]
|
||||||
|
for index, (step_title, step_desc) in enumerate(steps, start=1):
|
||||||
|
flow_card.append(self._build_guide_step(index, step_title, step_desc))
|
||||||
|
|
||||||
|
tips_card = self._build_guide_card(
|
||||||
|
_("Good to know"),
|
||||||
|
_("A few things save time once the basics are configured."),
|
||||||
|
[
|
||||||
|
_("Use search in the header to filter large libraries quickly"),
|
||||||
|
_("The menu button on each tile contains CE actions and table binding"),
|
||||||
|
_("Window transparency can be toggled in Appearance settings"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
content.append(tips_card)
|
||||||
|
|
||||||
|
actions = Gtk.Box(spacing=12)
|
||||||
|
actions.set_halign(Gtk.Align.CENTER)
|
||||||
|
actions.set_margin_top(12)
|
||||||
|
|
||||||
|
prefs_btn = Gtk.Button(label=_("Open Settings"))
|
||||||
|
prefs_btn.add_css_class("pill")
|
||||||
|
prefs_btn.connect("clicked", self._on_guide_preferences)
|
||||||
|
actions.append(prefs_btn)
|
||||||
|
|
||||||
|
start_btn = Gtk.Button(label=_("Open Library"))
|
||||||
|
start_btn.add_css_class("suggested-action")
|
||||||
|
start_btn.add_css_class("pill")
|
||||||
|
start_btn.connect("clicked", self._on_guide_close)
|
||||||
|
actions.append(start_btn)
|
||||||
|
content.append(actions)
|
||||||
|
|
||||||
|
return Adw.NavigationPage(child=toolbar_view, title=_("Welcome to Vual"), tag="guide")
|
||||||
|
|
||||||
|
def _build_guide_card(self, title: str, subtitle: str, items: list[str]) -> Gtk.Box:
|
||||||
|
"""Build a guide card with bullet rows."""
|
||||||
|
card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||||
|
card.add_css_class("guide-card")
|
||||||
|
|
||||||
|
title_label = Gtk.Label(label=title, xalign=0)
|
||||||
|
title_label.add_css_class("guide-card-title")
|
||||||
|
card.append(title_label)
|
||||||
|
|
||||||
|
subtitle_label = Gtk.Label(label=subtitle, xalign=0, wrap=True)
|
||||||
|
subtitle_label.add_css_class("guide-card-subtitle")
|
||||||
|
card.append(subtitle_label)
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
row = Gtk.Box(spacing=10)
|
||||||
|
row.add_css_class("guide-list-row")
|
||||||
|
|
||||||
|
bullet = Gtk.Label(label="•")
|
||||||
|
bullet.add_css_class("guide-bullet")
|
||||||
|
row.append(bullet)
|
||||||
|
|
||||||
|
item_label = Gtk.Label(label=item, xalign=0, wrap=True)
|
||||||
|
item_label.add_css_class("guide-list-text")
|
||||||
|
row.append(item_label)
|
||||||
|
card.append(row)
|
||||||
|
|
||||||
|
return card
|
||||||
|
|
||||||
|
def _build_guide_step(self, index: int, title: str, description: str) -> Gtk.Box:
|
||||||
|
"""Build a single step row for the guide."""
|
||||||
|
row = Gtk.Box(spacing=14)
|
||||||
|
row.add_css_class("guide-step")
|
||||||
|
|
||||||
|
badge = Gtk.Label(label=str(index))
|
||||||
|
badge.add_css_class("guide-step-badge")
|
||||||
|
badge.set_size_request(34, 34)
|
||||||
|
badge.set_valign(Gtk.Align.START)
|
||||||
|
row.append(badge)
|
||||||
|
|
||||||
|
text_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=3)
|
||||||
|
|
||||||
|
title_label = Gtk.Label(label=title, xalign=0)
|
||||||
|
title_label.add_css_class("guide-step-title")
|
||||||
|
text_box.append(title_label)
|
||||||
|
|
||||||
|
desc_label = Gtk.Label(label=description, xalign=0, wrap=True)
|
||||||
|
desc_label.add_css_class("guide-step-description")
|
||||||
|
text_box.append(desc_label)
|
||||||
|
|
||||||
|
row.append(text_box)
|
||||||
|
return row
|
||||||
|
|
||||||
|
def _on_guide_close(self, _btn: Gtk.Button) -> None:
|
||||||
|
"""Close guide and mark as shown."""
|
||||||
|
self.config.guide_shown = True
|
||||||
|
self.config.save()
|
||||||
|
self._nav_view.pop()
|
||||||
|
|
||||||
|
def _on_guide_preferences(self, _btn: Gtk.Button) -> None:
|
||||||
|
"""Open preferences from the guide."""
|
||||||
|
self._on_guide_close(_btn)
|
||||||
|
app = self.get_application()
|
||||||
|
if app is not None:
|
||||||
|
app.activate_action("preferences", None)
|
||||||
|
|||||||
@@ -367,40 +367,3 @@ def apply_theme_to_all(steamapps: Path, theme: str) -> tuple[int, int]:
|
|||||||
return (success, failed)
|
return (success, failed)
|
||||||
|
|
||||||
|
|
||||||
def get_current_theme(pfx: Path) -> str | None:
|
|
||||||
"""Detect current theme from prefix colors.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
"dark", "light", or None if unknown.
|
|
||||||
"""
|
|
||||||
user_reg = pfx / "user.reg"
|
|
||||||
if not user_reg.is_file():
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
content = user_reg.read_text(encoding="utf-8", errors="replace")
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Check Window background color
|
|
||||||
match = re.search(r'"Window"="([^"]+)"', content)
|
|
||||||
if not match:
|
|
||||||
return None
|
|
||||||
|
|
||||||
window_color = match.group(1)
|
|
||||||
|
|
||||||
# Dark themes typically have dark Window color
|
|
||||||
parts = window_color.split()
|
|
||||||
if len(parts) == 3:
|
|
||||||
try:
|
|
||||||
r, g, b = int(parts[0]), int(parts[1]), int(parts[2])
|
|
||||||
# If all RGB < 100, it's dark
|
|
||||||
if r < 100 and g < 100 and b < 100:
|
|
||||||
return "dark"
|
|
||||||
# If all RGB > 200, it's light
|
|
||||||
if r > 200 and g > 200 and b > 200:
|
|
||||||
return "light"
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|||||||
Reference in New Issue
Block a user