feat: intial work

This commit is contained in:
Tine Jozelj 2023-12-21 12:21:53 +01:00
parent be1d5b0fb3
commit bac018672a
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
35 changed files with 3060 additions and 311 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@
/.flatpak/
/vendor
/.vscode
/src/config.rs

2024
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,14 @@ lto = true
[dependencies]
gettext-rs = { version = "0.7", features = ["gettext-system"] }
gtk = { version = "0.7", package = "gtk4", features = ["v4_8"] }
gtk = { version = "0.7", package = "gtk4", features = ["v4_10"] }
tracing = "0.1.37"
tracing-subscriber = "0.3"
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
gsettings-macro = "0.1.20"
[dependencies.adw]
package = "libadwaita"
version = "0.5"
features = ["v1_4"]

View file

@ -1,7 +1,7 @@
{
"id": "dev.mnts.ModManager.Devel",
"runtime": "org.gnome.Platform",
"runtime-version": "44",
"runtime-version": "45",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
@ -19,9 +19,7 @@
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm16/bin",
"build-args": [
"--share=network"
],
"build-args": ["--share=network"],
"env": {
"CARGO_REGISTRIES_CRATES_IO_PROTOCOL": "sparse",
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang",
@ -29,19 +27,37 @@
"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"
]
"test-args": ["--socket=x11", "--share=network"]
},
"cleanup": [
"/include",
"/lib/pkgconfig",
"/man",
"/share/doc",
"/share/gtk-doc",
"/share/man",
"/share/pkgconfig",
"*.la",
"*.a"
],
"modules": [
{
"name": "blueprint-compiler",
"buildsystem": "meson",
"cleanup": ["*"],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
"tag": "v0.10.0"
}
]
},
{
"name": "mod-manager",
"buildsystem": "meson",
"run-tests": true,
"config-opts": [
"-Dprofile=development"
],
"config-opts": ["-Dprofile=development"],
"sources": [
{
"type": "dir",
@ -51,4 +67,3 @@
}
]
}

View file

@ -13,5 +13,10 @@
<default>false</default>
<summary>Window maximized state</summary>
</key>
<key name="games" type="{ss}">
<default>{}</default>
<summary>Dictionary of games being managed. First string is game name second is path to mods
folder.</summary>
</key>
</schema>
</schemalist>
</schemalist>

View file

@ -1,8 +1,20 @@
# Resources
blueprints = custom_target('blueprints',
input: files(
'ui/windows/main/main.blp',
'ui/windows/main/pages/welcome.blp',
'ui/windows/main/pages/games_and_mods.blp',
'ui/windows/add_new_game/add_new_game.blp',
'ui/components/mods_list.blp',
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
)
resources = gnome.compile_resources(
'resources',
'resources.gresource.xml',
gresource_bundle: true,
dependencies: blueprints,
source_dir: meson.current_build_dir(),
install: true,
install_dir: pkgdatadir,

View file

@ -2,8 +2,11 @@
<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">ui/windows/main/main.ui</file>
<file compressed="true">ui/windows/main/pages/welcome.ui</file>
<file compressed="true">ui/windows/main/pages/games_and_mods.ui</file>
<file compressed="true">ui/windows/add_new_game/add_new_game.ui</file>
<file compressed="true">ui/components/mods_list.ui</file>
<file compressed="true">style.css</file>
</gresource>
</gresources>

View file

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

View file

@ -0,0 +1,135 @@
using Gtk 4.0;
using Adw 1;
template $ModsList : Adw.NavigationPage {
title: _("Main");
Adw.ToolbarView {
[top]
Adw.HeaderBar header_bar {
show-back-button: true;
}
content: Box {
orientation: vertical;
valign: center;
halign: center;
Image {
name: "logo";
icon-name: "re.sonny.Workbench";
pixel-size: 196;
margin-bottom: 30;
styles [
"icon-dropshadow"
]
}
Label {
label: _("Welcome to Mod Manager");
margin-bottom: 30;
styles [
"title-1"
]
}
Box subtitle {
orientation: vertical;
halign: center;
margin-bottom: 30;
Label {
label: "Learn and prototype with\nGNOME technologies";
justify: center;
}
}
Box {
orientation: vertical;
homogeneous: true;
halign: center;
Box {
margin-bottom: 6;
Image {
icon-name: "update-symbolic";
margin-end: 12;
icon-size: normal;
}
Label {
label: _("Edit Style or UI to update the Preview");
}
}
Box {
margin-bottom: 6;
Image {
icon-name: "media-playback-start-symbolic";
margin-end: 12;
icon-size: normal;
}
Label {
label: _("Hit");
}
ShortcutsShortcut {
accelerator: "<Control>Return";
margin-start: 12;
}
Label {
label: _("to format and run Code");
}
}
Box {
margin-bottom: 6;
Image {
icon-name: "media-floppy-symbolic";
margin-end: 12;
icon-size: normal;
}
Label {
label: _("Changes are automatically saved and restored");
}
}
Box {
margin-bottom: 6;
Image {
icon-name: "library-symbolic";
margin-end: 12;
icon-size: normal;
}
Label {
label: _("Browse the Library for demos and examples");
}
}
Box {
margin-bottom: 6;
Image {
icon-name: "user-bookmarks-symbolic";
margin-end: 12;
icon-size: normal;
}
Label {
label: _("Checkout the Bookmarks menu to learn and get help");
}
}
}
};
}
}

View file

@ -1,29 +0,0 @@
<?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

@ -1,41 +0,0 @@
<?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>

View file

@ -0,0 +1,46 @@
using Gtk 4.0;
using Adw 1;
template $ModManagerWindowAddNewGame : Adw.ApplicationWindow {
default-width: 600;
default-height: 400;
title: _("Add a new game");
content: Adw.ToolbarView {
[top]
Adw.HeaderBar header_bar {
show-back-button: true;
}
content: Box {
orientation: vertical;
halign: center;
valign: center;
Label {
label: _("Chose a game to manage mods for");
margin-bottom: 30;
justify: center;
wrap: true;
styles ["title-1"]
}
DropDown games_dropdown {
margin-bottom: 30;
}
FileDialog mods_folder {
title: _("Chose a location where to install the mods");
}
Button chose_button {
label: _("Chose the location");
margin-bottom: 30;
}
Button complete_button {
label: _("Add game");
}
};
};
}

View file

@ -0,0 +1,25 @@
/*
Copyright (c) 2023 Tine Jozelj
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using Gtk 4.0;
using Adw 1;
template $ModManagerWindowMain : Adw.ApplicationWindow {
default-width: 1000;
default-height: 800;
title: _("Mod Manager");
}

View file

@ -0,0 +1,45 @@
using Gtk 4.0;
using Adw 1;
template $GamesAndMods: Adw.Bin {
Adw.NavigationSplitView {
sidebar: Adw.NavigationPage {
title: "Games";
tag: "sidebar";
Adw.ToolbarView {
[top]
Adw.HeaderBar {
}
content: Box {
Label {
label: "The Sims 4";
}
Label {
label: "The Sims 3";
}
Label {
label: "The Sims 2";
}
};
}
};
content: Adw.NavigationPage{
title: "Mods";
tag: "content";
Adw.ToolbarView {
[top]
Adw.HeaderBar {
}
content: Box {
Label {
label: "Mods";
}
};
}
};
}
}

View file

@ -0,0 +1,28 @@
using Gtk 4.0;
using Adw 1;
template $Welcome: Adw.Bin {
Adw.ToolbarView {
[top]
Adw.HeaderBar {
}
content: Box {
orientation: vertical;
halign: center;
valign: center;
Label {
label: "No games added yet!";
justify: center;
wrap: true;
styles ["title-1"]
margin-bottom: 30;
}
Button add_new_game {
label: "Add new game";
styles ["suggested-action", "pill"]
}
};
}
}

15
src/api/base.rs Normal file
View file

@ -0,0 +1,15 @@
pub const API_URL: &str = "https://api.curseforge.com";
pub fn get_curse_forge_client() -> Result<reqwest::blocking::Client, reqwest::Error> {
let mut api_key_header =
reqwest::header::HeaderValue::from_str(crate::config::API_KEY).unwrap();
api_key_header.set_sensitive(true);
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("x-api-key", api_key_header);
reqwest::blocking::Client::builder()
.user_agent(format!("dev.mnts.ModManager/{}", crate::config::VERSION))
.default_headers(headers)
.build()
}

46
src/api/games.rs Normal file
View file

@ -0,0 +1,46 @@
use serde::Deserialize;
use crate::api::*;
#[derive(Deserialize, Debug)]
struct Response {
data: Vec<Game>,
pagination: Pagination,
}
#[derive(Deserialize, Debug)]
struct Game {
id: u32,
name: String,
slug: String,
dateModified: String,
assets: Assets,
status: u32,
apiStatus: u32,
}
#[derive(Deserialize, Debug)]
struct Assets {
iconUrl: Option<String>,
tileUrl: String,
coverUrl: Option<String>,
}
#[derive(Deserialize, Debug)]
struct Pagination {
index: u32,
pageSize: u32,
resultCount: u32,
totalCount: u32,
}
pub fn get_games() -> Vec<String> {
let client = base::get_curse_forge_client().unwrap();
let response = client
.get(&format!("{}/v1/games", base::API_URL))
.send()
.unwrap();
let json: Response = response.json().unwrap();
return json.data.iter().map(|game| game.name.clone()).collect();
}

5
src/api/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod base;
pub use base::*;
pub mod games;
pub use games::*;

View file

@ -1,54 +1,87 @@
use gettextrs::gettext;
/* application.rs
*
* Copyright 2023 Tine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
use adw::subclass::prelude::*;
use gtk::{gdk, prelude::*};
use gtk::{gio, glib};
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;
use crate::config::VERSION;
use crate::config::{APP_ID, PKGDATADIR, PROFILE};
use crate::windows::main::ModManagerWindowMain;
use crate::windows::main::Welcome;
mod imp {
use super::*;
use glib::WeakRef;
use std::cell::OnceCell;
#[derive(Debug, Default)]
pub struct ExampleApplication {
pub window: OnceCell<WeakRef<ExampleApplicationWindow>>,
}
pub struct ModManagerApplication {}
#[glib::object_subclass]
impl ObjectSubclass for ExampleApplication {
const NAME: &'static str = "ExampleApplication";
type Type = super::ExampleApplication;
type ParentType = gtk::Application;
impl ObjectSubclass for ModManagerApplication {
const NAME: &'static str = "ModManagerApplication";
type Type = super::ModManagerApplication;
type ParentType = adw::Application;
}
impl ObjectImpl for ExampleApplication {}
impl ObjectImpl for ModManagerApplication {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.set_accels_for_action("app.quit", &["<primary>q"]);
impl ApplicationImpl for ExampleApplication {
//let settings = ModManagerSettings::default();
// let games = settings.games();
//let settings = gio::Settings::new(APP_ID);
//let games = settings.string("games");
//println!("Games: {:?}", games);
}
}
impl ApplicationImpl for ModManagerApplication {
// We connect to the activate callback to create a window when the application
// has been launched. Additionally, this callback notifies us when the user
// tries to launch a "second instance" of the application. When they try
// to do that, we'll just present any existing window.
fn activate(&self) {
debug!("GtkApplication<ExampleApplication>::activate");
self.parent_activate();
let app = self.obj();
debug!("GtkApplication<ModManager>::activate");
let application = self.obj();
// Get the current window or create one if necessary
let window = if let Some(window) = application.active_window() {
window
} else {
// TODO: Figure our if show welcome or games_and_mods.
let welcome = Welcome::new();
let window = ModManagerWindowMain::new(&*application, &welcome.upcast());
window.upcast()
};
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();
// Ask the window manager/compositor to present the window
window.present();
}
fn startup(&self) {
debug!("GtkApplication<ExampleApplication>::startup");
debug!("GtkApplication<ModManager>::startup");
self.parent_startup();
let app = self.obj();
@ -57,47 +90,43 @@ mod imp {
app.setup_css();
app.setup_gactions();
app.setup_accels();
}
}
impl GtkApplicationImpl for ExampleApplication {}
impl GtkApplicationImpl for ModManagerApplication {}
impl AdwApplicationImpl for ModManagerApplication {}
}
glib::wrapper! {
pub struct ExampleApplication(ObjectSubclass<imp::ExampleApplication>)
@extends gio::Application, gtk::Application,
@implements gio::ActionMap, gio::ActionGroup;
pub struct ModManagerApplication(ObjectSubclass<imp::ModManagerApplication>)
@extends gio::Application, gtk::Application, adw::Application,
@implements gio::ActionGroup, gio::ActionMap;
}
impl ExampleApplication {
fn main_window(&self) -> ExampleApplicationWindow {
self.imp().window.get().unwrap().upgrade().unwrap()
impl ModManagerApplication {
pub fn new(flags: &gio::ApplicationFlags) -> Self {
glib::Object::builder()
.property("application-id", APP_ID)
.property("flags", flags)
.build()
}
pub fn run(&self) -> glib::ExitCode {
info!("Mod Manager ({})", APP_ID);
info!("Version: {} ({})", VERSION, PROFILE);
info!("Datadir: {}", PKGDATADIR);
ApplicationExtManual::run(self)
}
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();
})
let quit_action = gio::ActionEntry::builder("quit")
.activate(move |app: &Self, _, _| app.quit())
.build();
// About
let action_about = gio::ActionEntry::builder("about")
.activate(|app: &Self, _, _| {
app.show_about_dialog();
})
let about_action = gio::ActionEntry::builder("about")
.activate(move |app: &Self, _, _| app.show_about())
.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"]);
self.add_action_entries([quit_action, about_action]);
}
fn setup_css(&self) {
@ -112,38 +141,18 @@ impl ExampleApplication {
}
}
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/")
fn show_about(&self) {
let window = self.active_window().unwrap();
let about = adw::AboutWindow::builder()
.transient_for(&window)
.application_name("mod-manager")
.application_icon(APP_ID)
.developer_name("Tine Jozelj")
.version(VERSION)
.transient_for(&self.main_window())
.translator_credits(gettext("translator-credits"))
.modal(true)
.authors(vec!["Tine Jozelj"])
.artists(vec!["Tine Jozelj"])
.developers(vec!["Tine Jozelj <tine@tjo.space>"])
.copyright("© 2023 Tine Jozelj and other developers")
.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()
about.present();
}
}

2
src/components/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod mods_list;
pub use mods_list::*;

View file

@ -0,0 +1,64 @@
/* welcome.rs
*
* Copyright 2023 Tine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
use adw::subclass::prelude::*;
use gtk::{gio, glib};
use gtk::{glib::clone, prelude::*};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/components/mods_list.ui")]
pub struct ModsList {}
#[glib::object_subclass]
impl ObjectSubclass for ModsList {
const NAME: &'static str = "ModsList";
type Type = super::ModsList;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ModsList {
fn constructed(&self) {}
}
impl WidgetImpl for ModsList {}
impl NavigationPageImpl for ModsList {}
}
glib::wrapper! {
pub struct ModsList(ObjectSubclass<imp::ModsList>)
@extends gtk::Widget, gtk::Buildable, adw::NavigationPage,
@implements gio::ActionGroup, gio::ActionMap;
}
impl ModsList {
pub fn new() -> Self {
glib::Object::builder().build()
}
}

View file

@ -5,3 +5,5 @@ pub const PKGDATADIR: &str = @PKGDATADIR@;
pub const PROFILE: &str = @PROFILE@;
pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource");
pub const VERSION: &str = @VERSION@;
pub const API_KEY: &str = @API_KEY@;

View file

@ -1,32 +1,56 @@
mod config {
#![allow(dead_code)]
/*
Copyright (c) 2023 Tine Jozelj
include!(concat!(env!("CODEGEN_BUILD_DIR"), "/config.rs"));
}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
mod api;
mod application;
mod window;
mod components;
mod config;
mod windows;
use self::application::ModManagerApplication;
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
use gettextrs::{gettext, LocaleCategory};
use gtk::prelude::*;
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
// Set up gettext translations
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);
// Load resources
let resources = gio::Resource::load(RESOURCES_FILE).expect("Could not load resources");
gio::resources_register(&resources);
let app = ExampleApplication::default();
// Create a new GtkApplication. The application manages our main loop,
// application windows, integration with the window manager/compositor, and
// desktop features such as file opening and single-instance applications.
let app = ModManagerApplication::new(&gio::ApplicationFlags::empty());
// Run the application. This function will block until the application
// exits. Upon return, we have our exit code to return to the shell. (This
// is the code you see when you do `echo $?` after running a command in a
// terminal.
app.run()
}

View file

@ -5,12 +5,21 @@ 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)
global_conf.set_quoted('API_KEY', '$2a$10$AtaQ/fkWxMoVHVhO.6PMoOHQq7ERdSdpmegqJ09.2Mgj5iTQP3r.2')
config = configure_file(
input: 'config.rs.in',
output: 'config.rs',
configuration: global_conf
)
# Copy the config.rs output to the source directory.
run_command(
'cp',
meson.project_build_root() / 'src' / 'config.rs',
meson.project_source_root() / 'src' / 'config.rs',
check: true
)
cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ]

66
src/mods_list.rs Normal file
View file

@ -0,0 +1,66 @@
/* welcome.rs
*
* Copyright 2023 Tine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
use adw::subclass::prelude::*;
use gtk::{gio, glib};
use gtk::{glib::clone, prelude::*};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/components/mods_list.ui")]
pub struct ModsList {
}
#[glib::object_subclass]
impl ObjectSubclass for ModsList {
const NAME: &'static str = "ModsList";
type Type = super::ModsList;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ModsList {
fn constructed(&self) {
}
}
impl WidgetImpl for ModsList {}
impl NavigationPageImpl for ModsList {}
}
glib::wrapper! {
pub struct ModsList(ObjectSubclass<imp::ModsList>)
@extends gtk::Widget, gtk::Buildable, adw::NavigationPage,
@implements gio::ActionGroup, gio::ActionMap;
}
impl ModsList {
pub fn new() -> Self {
glib::Object::builder().build()
}
}

20
src/settings.rs Normal file
View file

@ -0,0 +1,20 @@
use gio::glib;
use gsettings_macro::gen_settings;
use gtk::gio;
use std::collections::HashMap;
use crate::config::APP_ID;
#[gen_settings(file = "./data/dev.mnts.ModManager.gschema.xml.in")]
#[gen_settings_define(
key_name = "games",
arg_type = "HashMap<String, String>",
ret_type = "HashMap<String, String>"
)]
pub struct ModManagerSettings;
impl Default for ModManagerSettings {
fn default() -> Self {
Self::new(APP_ID)
}
}

View file

@ -1,117 +0,0 @@
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();
}
}
}

View file

@ -0,0 +1,96 @@
/* welcome.rs
*
* Copyright 2023 Tine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
use adw::subclass::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk::{gio, glib};
use crate::api::*;
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/windows/add_new_game/add_new_game.ui")]
pub struct ModManagerWindowAddNewGame {
#[template_child]
pub complete_button: TemplateChild<gtk::Button>,
#[template_child]
pub games_dropdown: TemplateChild<gtk::DropDown>,
}
#[glib::object_subclass]
impl ObjectSubclass for ModManagerWindowAddNewGame {
const NAME: &'static str = "ModManagerWindowAddNewGame";
type Type = super::ModManagerWindowAddNewGame;
type ParentType = adw::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ModManagerWindowAddNewGame {}
impl WidgetImpl for ModManagerWindowAddNewGame {}
impl WindowImpl for ModManagerWindowAddNewGame {}
impl ApplicationWindowImpl for ModManagerWindowAddNewGame {}
impl AdwApplicationWindowImpl for ModManagerWindowAddNewGame {}
}
glib::wrapper! {
pub struct ModManagerWindowAddNewGame(ObjectSubclass<imp::ModManagerWindowAddNewGame>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
@implements gio::ActionGroup, gio::ActionMap;
}
impl ModManagerWindowAddNewGame {
pub fn new() -> Self {
glib::Object::builder().build()
}
pub fn setup(&self) {
let games = games::get_games();
let games_strs: Vec<&str> = games.iter().map(|s| s.as_str()).collect();
let games_list = &gtk::StringList::new(&games_strs);
let complete_button = &imp::ModManagerWindowAddNewGame::from_obj(self).complete_button;
let games_dropdown = &imp::ModManagerWindowAddNewGame::from_obj(self).games_dropdown;
games_dropdown.set_property("model", games_list);
let instance = self;
complete_button.connect_clicked(clone!(@strong instance => move |_| {
//let selected_game = games_dropdown.selected_item();
println!("complete button clicked");
//if let Some(selected_game) = selected_game {
instance.hide()
//}
}));
}
}

View file

@ -0,0 +1,2 @@
pub mod add_new_game;
pub use add_new_game::*;

82
src/windows/main/main.rs Normal file
View file

@ -0,0 +1,82 @@
/*
Copyright (c) 2023 Tine Jozelj
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::{gio, glib};
use crate::config::PROFILE;
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/windows/main/main.ui")]
pub struct ModManagerWindowMain {}
#[glib::object_subclass]
impl ObjectSubclass for ModManagerWindowMain {
const NAME: &'static str = "ModManagerWindowMain";
type Type = super::ModManagerWindowMain;
type ParentType = adw::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ModManagerWindowMain {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
// Devel Profile
if PROFILE == "Devel" {
obj.add_css_class("devel");
}
}
fn dispose(&self) {
self.dispose_template();
}
}
impl WidgetImpl for ModManagerWindowMain {}
impl WindowImpl for ModManagerWindowMain {}
impl ApplicationWindowImpl for ModManagerWindowMain {}
impl AdwApplicationWindowImpl for ModManagerWindowMain {}
}
glib::wrapper! {
pub struct ModManagerWindowMain(ObjectSubclass<imp::ModManagerWindowMain>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
@implements gio::ActionGroup, gio::ActionMap;
}
impl ModManagerWindowMain {
pub fn new<P: glib::IsA<gtk::Application>>(application: &P, content: &gtk::Widget) -> Self {
glib::Object::builder()
.property("application", application)
.property("content", content)
.build()
}
}

5
src/windows/main/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod main;
pub use main::*;
pub mod pages;
pub use pages::*;

View file

@ -0,0 +1,60 @@
/*
Copyright (c) 2023 Tine Jozelj
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::{gio, glib};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/windows/main/pages/games_and_mods.ui")]
pub struct GamesAndMods {}
#[glib::object_subclass]
impl ObjectSubclass for GamesAndMods {
const NAME: &'static str = "GamesAndMods";
type Type = super::GamesAndMods;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for GamesAndMods {}
impl WidgetImpl for GamesAndMods {}
impl BinImpl for GamesAndMods {}
}
glib::wrapper! {
pub struct GamesAndMods(ObjectSubclass<imp::GamesAndMods>)
@extends gtk::Widget, adw::NavigationSplitView,
@implements gio::ActionGroup, gio::ActionMap;
}
impl GamesAndMods {
pub fn new() -> Self {
glib::Object::builder().build()
}
}

View file

@ -0,0 +1,5 @@
pub mod games_and_mods;
pub use games_and_mods::*;
pub mod welcome;
pub use welcome::*;

View file

@ -0,0 +1,76 @@
/*
Copyright (c) 2023 Tine Jozelj
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::{gio, glib};
use crate::windows::ModManagerWindowAddNewGame;
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/dev/mnts/ModManager/ui/windows/main/pages/welcome.ui")]
pub struct Welcome {
#[template_child]
pub add_new_game: gtk::TemplateChild<gtk::Button>,
}
#[glib::object_subclass]
impl ObjectSubclass for Welcome {
const NAME: &'static str = "Welcome";
type Type = super::Welcome;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for Welcome {
fn constructed(&self) {
self.parent_constructed();
self.add_new_game.connect_clicked(|_| {
let welcome = ModManagerWindowAddNewGame::new();
welcome.setup();
welcome.set_modal(true);
welcome.present();
});
}
}
impl WidgetImpl for Welcome {}
impl BinImpl for Welcome {}
}
glib::wrapper! {
pub struct Welcome(ObjectSubclass<imp::Welcome>)
@extends gtk::Widget, adw::ToolbarView,
@implements gio::ActionGroup, gio::ActionMap;
}
impl Welcome {
pub fn new() -> Self {
glib::Object::builder().build()
}
}

5
src/windows/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod main;
pub use main::*;
pub mod add_new_game;
pub use add_new_game::*;