Init with GTK Rust Template

This commit is contained in:
Tine Jozelj 2023-12-21 10:38:30 +01:00
commit be1d5b0fb3
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
33 changed files with 1277 additions and 0 deletions

21
.editorconfig Normal file
View file

@ -0,0 +1,21 @@
root = true
[*]
indent_style = space
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
[*.{build,css,doap,scss,ui,xml,xml.in,xml.in.in,yaml,yml}]
indent_size = 2
[*.{json,py,rs}]
indent_size = 4
[*.{c,h,h.in}]
indent_size = 2
max_line_length = 80
[NEWS]
indent_size = 2
max_line_length = 72

36
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,36 @@
on:
push:
branches: [main]
pull_request:
name: CI
jobs:
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt
- name: Run cargo fmt
run: cargo fmt --all -- --check
flatpak:
name: Flatpak
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-44
options: --privileged
steps:
- uses: actions/checkout@v3
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v6
with:
bundle: mod-manager.flatpak
manifest-path: build-aux/dev.mnts.ModManager.Devel.json
run-tests: true
cache-key: flatpak-builder-${{ github.sha }}

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
/target/
/build/
/_build/
/builddir/
/build-aux/app
/build-aux/.flatpak-builder/
*.ui.in~
*.ui~
/.flatpak/
/vendor
/.vscode

40
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,40 @@
stages:
- check
- test
flatpak:
image: 'quay.io/gnome_infrastructure/gnome-runtime-images:gnome-44'
stage: test
tags:
- flatpak
variables:
BUNDLE: "mod-manager-nightly.flatpak"
MANIFEST_PATH: "build-aux/dev.mnts.ModManager.Devel.json"
FLATPAK_MODULE: "mod-manager"
APP_ID: "dev.mnts.ModManager.Devel"
RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo"
script:
- flatpak install --user --noninteractive flathub org.freedesktop.Sdk.Extension.llvm16//22.08
- >
xvfb-run -a -s "-screen 0 1024x768x24"
flatpak-builder --keep-build-dirs --user --disable-rofiles-fuse flatpak_app --repo=repo ${BRANCH:+--default-branch=$BRANCH} ${MANIFEST_PATH}
- flatpak build-bundle repo ${BUNDLE} --runtime-repo=${RUNTIME_REPO} ${APP_ID} ${BRANCH}
artifacts:
name: 'Flatpak artifacts'
expose_as: 'Get Flatpak bundle here'
when: 'always'
paths:
- "${BUNDLE}"
- '.flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-logs/meson-log.txt'
- '.flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-logs/testlog.txt'
expire_in: 14 days
# Configure and run rustfmt
# Exits and builds fails if on bad format
rustfmt:
image: "rust:slim"
script:
- rustup component add rustfmt
- rustc -Vv && cargo -Vv
- cargo fmt --version
- cargo fmt --all -- --color=always --check

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "mod-manager"
version = "0.1.0"
authors = ["Tine Jozelj <tine@tjo.space>"]
edition = "2021"
[profile.release]
lto = true
[dependencies]
gettext-rs = { version = "0.7", features = ["gettext-system"] }
gtk = { version = "0.7", package = "gtk4", features = ["v4_8"] }
tracing = "0.1.37"
tracing-subscriber = "0.3"

77
README.md Normal file
View file

@ -0,0 +1,77 @@
# GTK + Rust + Meson + Flatpak = <3
A boilerplate template to get started with GTK, Rust, Meson, Flatpak made for GNOME. It can be adapted for other desktop environments like elementary.
<div align="center">
![Main window](data/resources/screenshots/screenshot1.png "Main window")
</div>
## What does it contains?
- A simple window with a headerbar
- Bunch of useful files that you SHOULD ship with your application on Linux:
- Metainfo: describe your application for the different application stores out there;
- Desktop: the application launcher;
- Icons: This repo contains three icons, a normal, a nightly & monochromatic icon (symbolic) per the GNOME HIG, exported using [App Icon Preview](https://flathub.org/apps/details/org.gnome.design.AppIconPreview).
- Flatpak Manifest for nightly builds
- Dual installation support
- Uses Meson for building the application
- Bundles the UI files & the CSS using gresources
- A pre-commit hook to run rustfmt on your code
- Tests to validate your Metainfo, Schemas & Desktop files
- Gsettings to store the window state, more settings could be added
- Gitlab CI to produce flatpak nightlies
- i18n support
## How to init a project ?
The template ships a simple python script to init a project easily. It asks you a few questions and replaces & renames all the necessary files.
The script requires having `git` installed on your system.
You can run it with,
```shell
python3 create-project.py
```
```shell
➜ python3 create-project.py
Welcome to GTK Rust Template
Name: Contrast
Project Name: contrast
Application ID (e.g. org.domain.MyAwesomeApp, see: https://developer.gnome.org/ChooseApplicationID/): org.gnome.design.Contrast
Author: Bilal Elmoussaoui
Email: bil.elmoussaoui@gmail.com
```
A new directory named `contrast` containing the generated project
## Building the project
Make sure you have `flatpak` and `flatpak-builder` installed. Then run the commands below. Replace `<application_id>` with the value you entered during project creation. Please note that these commands are just for demonstration purposes. Normally this would be handled by your IDE, such as GNOME Builder or VS Code with the Flatpak extension.
```
flatpak install --user org.gnome.Sdk//43 org.freedesktop.Sdk.Extension.rust-stable//22.08 org.gnome.Platform//43 org.freedesktop.Sdk.Extension.llvm14//22.08
flatpak-builder --user flatpak_app build-aux/<application_id>.Devel.json
```
## Running the project
Once the project is build, run the command below. Replace Replace `<application_id>` and `<project_name>` with the values you entered during project creation. Please note that these commands are just for demonstration purposes. Normally this would be handled by your IDE, such as GNOME Builder or VS Code with the Flatpak extension.
```
flatpak-builder --run flatpak_app build-aux/<application_id>.Devel.json <project_name>
```
## Community
Join the GNOME and gtk-rs community!
- [Matrix chat](https://matrix.to/#/#rust:gnome.org): chat with other developers using gtk-rs
- [Discourse forum](https://discourse.gnome.org/tag/rust): topics tagged with `rust` on the GNOME forum.
- [GNOME circle](https://circle.gnome.org/): take inspiration from applications and libraries already extending the GNOME ecosystem.
## Credits
- [Podcasts](https://gitlab.gnome.org/World/podcasts)
- [Shortwave](https://gitlab.gnome.org/World/Shortwave)

View file

@ -0,0 +1,54 @@
{
"id": "dev.mnts.ModManager.Devel",
"runtime": "org.gnome.Platform",
"runtime-version": "44",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm16"
],
"command": "mod-manager",
"finish-args": [
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
"--env=RUST_LOG=mod_manager=debug",
"--env=G_MESSAGES_DEBUG=none",
"--env=RUST_BACKTRACE=1"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm16/bin",
"build-args": [
"--share=network"
],
"env": {
"CARGO_REGISTRIES_CRATES_IO_PROTOCOL": "sparse",
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang",
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold",
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER": "clang",
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold"
},
"test-args": [
"--socket=x11",
"--share=network"
]
},
"modules": [
{
"name": "mod-manager",
"buildsystem": "meson",
"run-tests": true,
"config-opts": [
"-Dprofile=development"
],
"sources": [
{
"type": "dir",
"path": "../"
}
]
}
]
}

16
build-aux/dist-vendor.sh Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
# Since Meson invokes this script as
# "/bin/sh .../dist-vendor.sh DIST SOURCE_ROOT" we can't rely on bash features
set -eu
export DIST="$1"
export SOURCE_ROOT="$2"
cd "$SOURCE_ROOT"
mkdir "$DIST"/.cargo
cargo vendor > "$DIST/.cargo/config"
# Don't combine the previous and this line with a pipe because we can't catch
# errors with "set -o pipefail"
sed -i 's/^directory = ".*"/directory = "vendor"/g' "$DIST/.cargo/config"
# Move vendor into dist tarball directory
mv vendor "$DIST"

View file

@ -0,0 +1,12 @@
[Desktop Entry]
Name=Mod Manager
Comment=Write a GTK + Rust application
Type=Application
Exec=mod-manager
Terminal=false
Categories=GNOME;GTK;
# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
Keywords=Gnome;GTK;
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
Icon=@icon@
StartupNotify=true

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<schemalist>
<schema path="/dev/mnts/ModManager/" id="@app-id@" gettext-domain="@gettext-package@">
<key name="window-width" type="i">
<default>600</default>
<summary>Window width</summary>
</key>
<key name="window-height" type="i">
<default>400</default>
<summary>Window height</summary>
</key>
<key name="is-maximized" type="b">
<default>false</default>
<summary>Window maximized state</summary>
</key>
</schema>
</schemalist>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Tine Jozelj 2019 <tine@tjo.space> -->
<component type="desktop-application">
<id>@app-id@</id>
<metadata_license>CC0</metadata_license>
<!-- Insert your license of choice here -->
<!-- <project_license>MIT</project_license> -->
<name>Mod Manager</name>
<summary>Write a GTK + Rust application</summary>
<description>
<p>A boilerplate template for GTK + Rust. It uses Meson as a build system and has flatpak support by default.</p>
</description>
<screenshots>
<screenshot type="default">
<image>https://gitlab.gnome.org/bilelmoussaoui/mod-manager/raw/master/data/resources/screenshots/screenshot1.png</image>
<caption>Main window</caption>
</screenshot>
</screenshots>
<url type="homepage">https://gitlab.gnome.org/bilelmoussaoui/mod-manager</url>
<url type="bugtracker">https://gitlab.gnome.org/bilelmoussaoui/mod-manager/issues</url>
<content_rating type="oars-1.0" />
<releases>
<release version="0.1.0" date="2019-07-11" />
</releases>
<kudos>
<!--
GNOME Software kudos:
https://gitlab.gnome.org/GNOME/gnome-software/-/blob/main/doc/kudos.md
-->
<kudo>ModernToolkit</kudo>
<kudo>HiDpiIcon</kudo>
</kudos>
<developer_name>Tine Jozelj</developer_name>
<update_contact>tine@tjo.space</update_contact>
<translation type="gettext">@gettext-package@</translation>
<launchable type="desktop-id">@app-id@.desktop</launchable>
</component>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
<defs>
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="mask0">
<g filter="url(#alpha)">
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip1">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10818" clip-path="url(#clip1)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask1">
<g filter="url(#alpha)">
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip2">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10821" clip-path="url(#clip2)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask2">
<g filter="url(#alpha)">
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip3">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10824" clip-path="url(#clip3)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask3">
<g filter="url(#alpha)">
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip4">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10827" clip-path="url(#clip4)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
</defs>
<g id="surface10764">
<rect x="0" y="0" width="16" height="16" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
<use xlink:href="#surface10818" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask0)"/>
<use xlink:href="#surface10821" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask1)"/>
<use xlink:href="#surface10824" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask2)"/>
<use xlink:href="#surface10827" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask3)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
<defs>
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="mask0">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip1">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10726" clip-path="url(#clip1)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask1">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip2">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10729" clip-path="url(#clip2)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask2">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip3">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10732" clip-path="url(#clip3)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask3">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip4">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10735" clip-path="url(#clip4)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask5">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip7">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10726" clip-path="url(#clip7)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask6">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip8">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10729" clip-path="url(#clip8)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask7">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip9">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10732" clip-path="url(#clip9)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask8">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip10">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10735" clip-path="url(#clip10)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<clipPath id="clip6">
<rect x="0" y="0" width="128" height="128"/>
</clipPath>
<g id="surface10750" clip-path="url(#clip6)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
<use xlink:href="#surface10726" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask5)"/>
<use xlink:href="#surface10729" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask6)"/>
<use xlink:href="#surface10732" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask7)"/>
<use xlink:href="#surface10735" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask8)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
</g>
<clipPath id="clip5">
<rect x="0" y="0" width="128" height="128"/>
</clipPath>
<g id="surface10753" clip-path="url(#clip5)" filter="url(#alpha)">
<use xlink:href="#surface10750"/>
</g>
<mask id="mask4">
<use xlink:href="#surface10753"/>
</mask>
<mask id="mask9">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.8;stroke:none;"/>
</g>
</mask>
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="300" y1="235" x2="428" y2="235" gradientTransform="matrix(0.000000000000000023,0.37,-0.98462,0.00000000000000006,295.38501,-30.360001)">
<stop offset="0" style="stop-color:rgb(97.647059%,94.117647%,41.960785%);stop-opacity:1;"/>
<stop offset="1" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
</linearGradient>
<clipPath id="clip12">
<rect x="0" y="0" width="128" height="128"/>
</clipPath>
<g id="surface10747" clip-path="url(#clip12)">
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 128 80.640625 L 128 128 L 0 128 L 0 80.640625 Z M 128 80.640625 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 13.308594 80.640625 L 60.664062 128 L 81.878906 128 L 34.519531 80.640625 Z M 55.730469 80.640625 L 103.09375 128 L 124.308594 128 L 76.945312 80.640625 Z M 98.160156 80.640625 L 128 110.480469 L 128 89.269531 L 119.371094 80.640625 Z M 0 88.546875 L 0 109.761719 L 18.238281 128 L 39.453125 128 Z M 0 88.546875 "/>
</g>
<clipPath id="clip11">
<rect x="0" y="0" width="128" height="128"/>
</clipPath>
<g id="surface10752" clip-path="url(#clip11)">
<use xlink:href="#surface10747" mask="url(#mask9)"/>
</g>
</defs>
<g id="surface10672">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
<use xlink:href="#surface10726" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask0)"/>
<use xlink:href="#surface10729" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask1)"/>
<use xlink:href="#surface10732" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask2)"/>
<use xlink:href="#surface10735" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask3)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
<use xlink:href="#surface10752" mask="url(#mask4)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
<defs>
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="mask0">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip1">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10632" clip-path="url(#clip1)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask1">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip2">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10635" clip-path="url(#clip2)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask2">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip3">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10638" clip-path="url(#clip3)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask3">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip4">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10641" clip-path="url(#clip4)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
</defs>
<g id="surface10578">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
<use xlink:href="#surface10632" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask0)"/>
<use xlink:href="#surface10635" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask1)"/>
<use xlink:href="#surface10638" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask2)"/>
<use xlink:href="#surface10641" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask3)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

10
data/icons/meson.build Normal file
View file

@ -0,0 +1,10 @@
install_data(
'@0@.svg'.format(application_id),
install_dir: iconsdir / 'hicolor' / 'scalable' / 'apps'
)
install_data(
'@0@-symbolic.svg'.format(base_id),
install_dir: iconsdir / 'hicolor' / 'symbolic' / 'apps',
rename: '@0@-symbolic.svg'.format(application_id)
)

76
data/meson.build Normal file
View file

@ -0,0 +1,76 @@
subdir('icons')
subdir('resources')
# Desktop file
desktop_conf = configuration_data()
desktop_conf.set('icon', application_id)
desktop_file = i18n.merge_file(
type: 'desktop',
input: configure_file(
input: '@0@.desktop.in.in'.format(base_id),
output: '@BASENAME@',
configuration: desktop_conf
),
output: '@0@.desktop'.format(application_id),
po_dir: podir,
install: true,
install_dir: datadir / 'applications'
)
# Validate Desktop file
if desktop_file_validate.found()
test(
'validate-desktop',
desktop_file_validate,
args: [
desktop_file.full_path()
],
depends: desktop_file,
)
endif
# Appdata
appdata_conf = configuration_data()
appdata_conf.set('app-id', application_id)
appdata_conf.set('gettext-package', gettext_package)
appdata_file = i18n.merge_file(
input: configure_file(
input: '@0@.metainfo.xml.in.in'.format(base_id),
output: '@BASENAME@',
configuration: appdata_conf
),
output: '@0@.metainfo.xml'.format(application_id),
po_dir: podir,
install: true,
install_dir: datadir / 'metainfo'
)
# Validate Appdata
if appstream_util.found()
test(
'validate-appdata', appstream_util,
args: [
'validate', '--nonet', appdata_file.full_path()
],
depends: appdata_file,
)
endif
# GSchema
gschema_conf = configuration_data()
gschema_conf.set('app-id', application_id)
gschema_conf.set('gettext-package', gettext_package)
configure_file(
input: '@0@.gschema.xml.in'.format(base_id),
output: '@0@.gschema.xml'.format(application_id),
configuration: gschema_conf,
install: true,
install_dir: datadir / 'glib-2.0' / 'schemas'
)
# Validata GSchema
if glib_compile_schemas.found()
test(
'validate-gschema', glib_compile_schemas,
args: [
'--strict', '--dry-run', meson.current_build_dir()
],
)
endif

View file

@ -0,0 +1,9 @@
# Resources
resources = gnome.compile_resources(
'resources',
'resources.gresource.xml',
gresource_bundle: true,
source_dir: meson.current_build_dir(),
install: true,
install_dir: pkgdatadir,
)

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/dev/mnts/ModManager/">
<!-- see https://gtk-rs.org/gtk4-rs/git/docs/gtk4/struct.Application.html#automatic-resources -->
<file compressed="true" preprocess="xml-stripblanks" alias="gtk/help-overlay.ui">ui/shortcuts.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/window.ui</file>
<file compressed="true">style.css</file>
</gresource>
</gresources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

4
data/resources/style.css Normal file
View file

@ -0,0 +1,4 @@
.title-header{
font-size: 36px;
font-weight: bold;
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkShortcutsWindow" id="help_overlay">
<property name="modal">True</property>
<child>
<object class="GtkShortcutsSection">
<property name="section-name">shortcuts</property>
<property name="max-height">10</property>
<child>
<object class="GtkShortcutsGroup">
<property name="title" translatable="yes" context="shortcut window">General</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes" context="shortcut window">Show Shortcuts</property>
<property name="action-name">win.show-help-overlay</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes" context="shortcut window">Quit</property>
<property name="action-name">app.quit</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="primary_menu">
<section>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
<attribute name="action">win.show-help-overlay</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About Mod Manager</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
<template class="ExampleApplicationWindow" parent="GtkApplicationWindow">
<child type="titlebar">
<object class="GtkHeaderBar" id="headerbar">
<child type="end">
<object class="GtkMenuButton" id="appmenu_button">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">primary_menu</property>
<property name="primary">True</property>
<property name="tooltip-text" translatable="yes">Main Menu</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="label" translatable="yes">Hello world!</property>
<style>
<class name="title-header"/>
</style>
</object>
</child>
</template>
</interface>

57
hooks/pre-commit.hook Executable file
View file

@ -0,0 +1,57 @@
#!/bin/sh
# Source: https://gitlab.gnome.org/GNOME/fractal/blob/master/hooks/pre-commit.hook
install_rustfmt() {
if ! which rustup >/dev/null 2>&1; then
curl https://sh.rustup.rs -sSf | sh -s -- -y
export PATH=$PATH:$HOME/.cargo/bin
if ! which rustup >/dev/null 2>&1; then
echo "Failed to install rustup. Performing the commit without style checking."
exit 0
fi
fi
if ! rustup component list|grep rustfmt >/dev/null 2>&1; then
echo "Installing rustfmt…"
rustup component add rustfmt
fi
}
if ! which cargo >/dev/null 2>&1 || ! cargo fmt --help >/dev/null 2>&1; then
echo "Unable to check the projects code style, because rustfmt could not be run."
if [ ! -t 1 ]; then
# No input is possible
echo "Performing commit."
exit 0
fi
echo ""
echo "y: Install rustfmt via rustup"
echo "n: Don't install rustfmt and perform the commit"
echo "Q: Don't install rustfmt and abort the commit"
echo ""
while true
do
printf "%s" "Install rustfmt via rustup? [y/n/Q]: "; read yn < /dev/tty
case $yn in
[Yy]* ) install_rustfmt; break;;
[Nn]* ) echo "Performing commit."; exit 0;;
[Qq]* | "" ) echo "Aborting commit."; exit 1 >/dev/null 2>&1;;
* ) echo "Invalid input";;
esac
done
fi
echo "--Checking style--"
cargo fmt --all -- --check
if test $? != 0; then
echo "--Checking style fail--"
echo "Please fix the above issues, either manually or by running: cargo fmt --all"
exit 1
else
echo "--Checking style pass--"
fi

71
meson.build Normal file
View file

@ -0,0 +1,71 @@
project(
'mod-manager',
'rust',
version: '0.1.0',
meson_version: '>= 0.59',
# license: 'MIT',
)
i18n = import('i18n')
gnome = import('gnome')
base_id = 'dev.mnts.ModManager'
dependency('glib-2.0', version: '>= 2.66')
dependency('gio-2.0', version: '>= 2.66')
dependency('gtk4', version: '>= 4.0.0')
glib_compile_resources = find_program('glib-compile-resources', required: true)
glib_compile_schemas = find_program('glib-compile-schemas', required: true)
desktop_file_validate = find_program('desktop-file-validate', required: false)
appstream_util = find_program('appstream-util', required: false)
cargo = find_program('cargo', required: true)
version = meson.project_version()
prefix = get_option('prefix')
bindir = prefix / get_option('bindir')
localedir = prefix / get_option('localedir')
datadir = prefix / get_option('datadir')
pkgdatadir = datadir / meson.project_name()
iconsdir = datadir / 'icons'
podir = meson.project_source_root() / 'po'
gettext_package = meson.project_name()
if get_option('profile') == 'development'
profile = 'Devel'
vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD', check: false).stdout().strip()
if vcs_tag == ''
version_suffix = '-devel'
else
version_suffix = '-@0@'.format(vcs_tag)
endif
application_id = '@0@.@1@'.format(base_id, profile)
else
profile = ''
version_suffix = ''
application_id = base_id
endif
meson.add_dist_script(
'build-aux/dist-vendor.sh',
meson.project_build_root() / 'meson-dist' / meson.project_name() + '-' + version,
meson.project_source_root()
)
if get_option('profile') == 'development'
# Setup pre-commit hook for ensuring coding style is always consistent
message('Setting up git pre-commit hook..')
run_command('cp', '-f', 'hooks/pre-commit.hook', '.git/hooks/pre-commit', check: false)
endif
subdir('data')
subdir('po')
subdir('src')
gnome.post_install(
gtk_update_icon_cache: true,
glib_compile_schemas: true,
update_desktop_database: true,
)

10
meson_options.txt Normal file
View file

@ -0,0 +1,10 @@
option(
'profile',
type: 'combo',
choices: [
'default',
'development'
],
value: 'default',
description: 'The build profile for Mod Manager. One of "default" or "development".'
)

0
po/LINGUAS Normal file
View file

6
po/POTFILES.in Normal file
View file

@ -0,0 +1,6 @@
data/dev.mnts.ModManager.desktop.in.in
data/dev.mnts.ModManager.gschema.xml.in
data/dev.mnts.ModManager.metainfo.xml.in.in
data/resources/ui/shortcuts.ui
data/resources/ui/window.ui
src/application.rs

1
po/meson.build Normal file
View file

@ -0,0 +1 @@
i18n.gettext(gettext_package, preset: 'glib')

149
src/application.rs Normal file
View file

@ -0,0 +1,149 @@
use gettextrs::gettext;
use tracing::{debug, info};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gdk, gio, glib};
use crate::config::{APP_ID, PKGDATADIR, PROFILE, VERSION};
use crate::window::ExampleApplicationWindow;
mod imp {
use super::*;
use glib::WeakRef;
use std::cell::OnceCell;
#[derive(Debug, Default)]
pub struct ExampleApplication {
pub window: OnceCell<WeakRef<ExampleApplicationWindow>>,
}
#[glib::object_subclass]
impl ObjectSubclass for ExampleApplication {
const NAME: &'static str = "ExampleApplication";
type Type = super::ExampleApplication;
type ParentType = gtk::Application;
}
impl ObjectImpl for ExampleApplication {}
impl ApplicationImpl for ExampleApplication {
fn activate(&self) {
debug!("GtkApplication<ExampleApplication>::activate");
self.parent_activate();
let app = self.obj();
if let Some(window) = self.window.get() {
let window = window.upgrade().unwrap();
window.present();
return;
}
let window = ExampleApplicationWindow::new(&app);
self.window
.set(window.downgrade())
.expect("Window already set.");
app.main_window().present();
}
fn startup(&self) {
debug!("GtkApplication<ExampleApplication>::startup");
self.parent_startup();
let app = self.obj();
// Set icons for shell
gtk::Window::set_default_icon_name(APP_ID);
app.setup_css();
app.setup_gactions();
app.setup_accels();
}
}
impl GtkApplicationImpl for ExampleApplication {}
}
glib::wrapper! {
pub struct ExampleApplication(ObjectSubclass<imp::ExampleApplication>)
@extends gio::Application, gtk::Application,
@implements gio::ActionMap, gio::ActionGroup;
}
impl ExampleApplication {
fn main_window(&self) -> ExampleApplicationWindow {
self.imp().window.get().unwrap().upgrade().unwrap()
}
fn setup_gactions(&self) {
// Quit
let action_quit = gio::ActionEntry::builder("quit")
.activate(move |app: &Self, _, _| {
// This is needed to trigger the delete event and saving the window state
app.main_window().close();
app.quit();
})
.build();
// About
let action_about = gio::ActionEntry::builder("about")
.activate(|app: &Self, _, _| {
app.show_about_dialog();
})
.build();
self.add_action_entries([action_quit, action_about]);
}
// Sets up keyboard shortcuts
fn setup_accels(&self) {
self.set_accels_for_action("app.quit", &["<Control>q"]);
self.set_accels_for_action("window.close", &["<Control>w"]);
}
fn setup_css(&self) {
let provider = gtk::CssProvider::new();
provider.load_from_resource("/dev/mnts/ModManager/style.css");
if let Some(display) = gdk::Display::default() {
gtk::style_context_add_provider_for_display(
&display,
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
}
}
fn show_about_dialog(&self) {
let dialog = gtk::AboutDialog::builder()
.logo_icon_name(APP_ID)
// Insert your license of choice here
// .license_type(gtk::License::MitX11)
// Insert your website here
// .website("https://gitlab.gnome.org/bilelmoussaoui/mod-manager/")
.version(VERSION)
.transient_for(&self.main_window())
.translator_credits(gettext("translator-credits"))
.modal(true)
.authors(vec!["Tine Jozelj"])
.artists(vec!["Tine Jozelj"])
.build();
dialog.present();
}
pub fn run(&self) -> glib::ExitCode {
info!("Mod Manager ({})", APP_ID);
info!("Version: {} ({})", VERSION, PROFILE);
info!("Datadir: {}", PKGDATADIR);
ApplicationExtManual::run(self)
}
}
impl Default for ExampleApplication {
fn default() -> Self {
glib::Object::builder()
.property("application-id", APP_ID)
.property("resource-base-path", "/dev/mnts/ModManager/")
.build()
}
}

7
src/config.rs.in Normal file
View file

@ -0,0 +1,7 @@
pub const APP_ID: &str = @APP_ID@;
pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
pub const LOCALEDIR: &str = @LOCALEDIR@;
pub const PKGDATADIR: &str = @PKGDATADIR@;
pub const PROFILE: &str = @PROFILE@;
pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource");
pub const VERSION: &str = @VERSION@;

32
src/main.rs Normal file
View file

@ -0,0 +1,32 @@
mod config {
#![allow(dead_code)]
include!(concat!(env!("CODEGEN_BUILD_DIR"), "/config.rs"));
}
mod application;
mod window;
use gettextrs::{gettext, LocaleCategory};
use gtk::{gio, glib};
use self::application::ExampleApplication;
use self::config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
fn main() -> glib::ExitCode {
// Initialize logger
tracing_subscriber::fmt::init();
// Prepare i18n
gettextrs::setlocale(LocaleCategory::LcAll, "");
gettextrs::bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain");
gettextrs::textdomain(GETTEXT_PACKAGE).expect("Unable to switch to the text domain");
glib::set_application_name(&gettext("Mod Manager"));
let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file");
gio::resources_register(&res);
let app = ExampleApplication::default();
app.run()
}

48
src/meson.build Normal file
View file

@ -0,0 +1,48 @@
global_conf = configuration_data()
global_conf.set_quoted('APP_ID', application_id)
global_conf.set_quoted('PKGDATADIR', pkgdatadir)
global_conf.set_quoted('PROFILE', profile)
global_conf.set_quoted('VERSION', version + version_suffix)
global_conf.set_quoted('GETTEXT_PACKAGE', gettext_package)
global_conf.set_quoted('LOCALEDIR', localedir)
config = configure_file(
input: 'config.rs.in',
output: 'config.rs',
configuration: global_conf
)
cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ]
if get_option('profile') == 'default'
cargo_options += [ '--release' ]
rust_target = 'release'
message('Building in release mode')
else
rust_target = 'debug'
message('Building in debug mode')
endif
cargo_env = [
'CARGO_HOME=' + meson.project_build_root() / 'cargo-home',
'CODEGEN_BUILD_DIR=' + meson.current_build_dir()
]
cargo_build = custom_target(
'cargo-build',
build_by_default: true,
build_always_stale: true,
output: meson.project_name(),
console: true,
install: true,
install_dir: bindir,
depends: resources,
command: [
'env',
cargo_env,
cargo, 'build',
cargo_options,
'&&',
'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
]
)

117
src/window.rs Normal file
View file

@ -0,0 +1,117 @@
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gio, glib};
use crate::application::ExampleApplication;
use crate::config::{APP_ID, PROFILE};
mod imp {
use super::*;
#[derive(Debug, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/window.ui")]
pub struct ExampleApplicationWindow {
#[template_child]
pub headerbar: TemplateChild<gtk::HeaderBar>,
pub settings: gio::Settings,
}
impl Default for ExampleApplicationWindow {
fn default() -> Self {
Self {
headerbar: TemplateChild::default(),
settings: gio::Settings::new(APP_ID),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for ExampleApplicationWindow {
const NAME: &'static str = "ExampleApplicationWindow";
type Type = super::ExampleApplicationWindow;
type ParentType = gtk::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
// You must call `Widget`'s `init_template()` within `instance_init()`.
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ExampleApplicationWindow {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
// Devel Profile
if PROFILE == "Devel" {
obj.add_css_class("devel");
}
// Load latest window state
obj.load_window_size();
}
fn dispose(&self) {
self.dispose_template();
}
}
impl WidgetImpl for ExampleApplicationWindow {}
impl WindowImpl for ExampleApplicationWindow {
// Save window state on delete event
fn close_request(&self) -> glib::Propagation {
if let Err(err) = self.obj().save_window_size() {
tracing::warn!("Failed to save window state, {}", &err);
}
// Pass close request on to the parent
self.parent_close_request()
}
}
impl ApplicationWindowImpl for ExampleApplicationWindow {}
}
glib::wrapper! {
pub struct ExampleApplicationWindow(ObjectSubclass<imp::ExampleApplicationWindow>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow,
@implements gio::ActionMap, gio::ActionGroup, gtk::Root;
}
impl ExampleApplicationWindow {
pub fn new(app: &ExampleApplication) -> Self {
glib::Object::builder().property("application", app).build()
}
fn save_window_size(&self) -> Result<(), glib::BoolError> {
let imp = self.imp();
let (width, height) = self.default_size();
imp.settings.set_int("window-width", width)?;
imp.settings.set_int("window-height", height)?;
imp.settings
.set_boolean("is-maximized", self.is_maximized())?;
Ok(())
}
fn load_window_size(&self) {
let imp = self.imp();
let width = imp.settings.int("window-width");
let height = imp.settings.int("window-height");
let is_maximized = imp.settings.boolean("is-maximized");
self.set_default_size(width, height);
if is_maximized {
self.maximize();
}
}
}