Initial commit

This commit is contained in:
Elias Projahn 2023-06-18 14:02:21 +02:00
parent 9969fb065e
commit 9969ebcfd7
25 changed files with 2250 additions and 0 deletions

112
src/application.rs Normal file
View file

@ -0,0 +1,112 @@
/* application.rs
*
* Copyright 2023 Unknown
*
* 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 gtk::prelude::*;
use adw::subclass::prelude::*;
use gtk::{gio, glib};
use crate::config::VERSION;
use crate::MusicusWindow;
mod imp {
use super::*;
#[derive(Debug, Default)]
pub struct MusicusApplication {}
#[glib::object_subclass]
impl ObjectSubclass for MusicusApplication {
const NAME: &'static str = "MusicusApplication";
type Type = super::MusicusApplication;
type ParentType = adw::Application;
}
impl ObjectImpl for MusicusApplication {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.setup_gactions();
obj.set_accels_for_action("app.quit", &["<primary>q"]);
}
}
impl ApplicationImpl for MusicusApplication {
// 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) {
let application = self.obj();
// Get the current window or create one if necessary
let window = if let Some(window) = application.active_window() {
window
} else {
let window = MusicusWindow::new(&*application);
window.upcast()
};
// Ask the window manager/compositor to present the window
window.present();
}
}
impl GtkApplicationImpl for MusicusApplication {}
impl AdwApplicationImpl for MusicusApplication {}
}
glib::wrapper! {
pub struct MusicusApplication(ObjectSubclass<imp::MusicusApplication>)
@extends gio::Application, gtk::Application, adw::Application,
@implements gio::ActionGroup, gio::ActionMap;
}
impl MusicusApplication {
pub fn new(application_id: &str, flags: &gio::ApplicationFlags) -> Self {
glib::Object::builder()
.property("application-id", application_id)
.property("flags", flags)
.build()
}
fn setup_gactions(&self) {
let quit_action = gio::ActionEntry::builder("quit")
.activate(move |app: &Self, _, _| app.quit())
.build();
let about_action = gio::ActionEntry::builder("about")
.activate(move |app: &Self, _, _| app.show_about())
.build();
self.add_action_entries([quit_action, about_action]);
}
fn show_about(&self) {
let window = self.active_window().unwrap();
let about = adw::AboutWindow::builder()
.transient_for(&window)
.application_name("musicus")
.application_icon("de.johrpan.musicus")
.developer_name("Unknown")
.version(VERSION)
.developers(vec!["Unknown"])
.copyright("© 2023 Unknown")
.build();
about.present();
}
}

4
src/config.rs Normal file
View file

@ -0,0 +1,4 @@
pub static VERSION: &str = "0.1.0";
pub static GETTEXT_PACKAGE: &str = "musicus";
pub static LOCALEDIR: &str = "/app/share/locale";
pub static PKGDATADIR: &str = "/app/share/musicus";

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

@ -0,0 +1,4 @@
pub static VERSION: &str = @VERSION@;
pub static GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
pub static LOCALEDIR: &str = @LOCALEDIR@;
pub static PKGDATADIR: &str = @PKGDATADIR@;

29
src/gtk/help-overlay.ui Normal file
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>

55
src/main.rs Normal file
View file

@ -0,0 +1,55 @@
/* main.rs
*
* Copyright 2023 Unknown
*
* 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
*/
mod application;
mod config;
mod window;
use self::application::MusicusApplication;
use self::window::MusicusWindow;
use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR};
use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain};
use gtk::{gio, glib};
use gtk::prelude::*;
fn main() -> glib::ExitCode {
// Set up gettext translations
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain");
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8")
.expect("Unable to set the text domain encoding");
textdomain(GETTEXT_PACKAGE).expect("Unable to switch to the text domain");
// Load resources
let resources = gio::Resource::load(PKGDATADIR.to_owned() + "/musicus.gresource")
.expect("Could not load resources");
gio::resources_register(&resources);
// 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 = MusicusApplication::new("de.johrpan.musicus", &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()
}

56
src/meson.build Normal file
View file

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

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/de/johrpan/musicus">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
</gresource>
</gresources>

71
src/window.rs Normal file
View file

@ -0,0 +1,71 @@
/* window.rs
*
* Copyright 2023 Unknown
*
* 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 gtk::prelude::*;
use adw::subclass::prelude::*;
use gtk::{gio, glib};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(resource = "/de/johrpan/musicus/window.ui")]
pub struct MusicusWindow {
// Template widgets
#[template_child]
pub header_bar: TemplateChild<gtk::HeaderBar>,
#[template_child]
pub label: TemplateChild<gtk::Label>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusWindow {
const NAME: &'static str = "MusicusWindow";
type Type = super::MusicusWindow;
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 MusicusWindow {}
impl WidgetImpl for MusicusWindow {}
impl WindowImpl for MusicusWindow {}
impl ApplicationWindowImpl for MusicusWindow {}
impl AdwApplicationWindowImpl for MusicusWindow {}
}
glib::wrapper! {
pub struct MusicusWindow(ObjectSubclass<imp::MusicusWindow>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap;
}
impl MusicusWindow {
pub fn new<P: glib::IsA<gtk::Application>>(application: &P) -> Self {
glib::Object::builder()
.property("application", application)
.build()
}
}

49
src/window.ui Normal file
View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="Adw" version="1.0"/>
<template class="MusicusWindow" parent="AdwApplicationWindow">
<property name="default-width">600</property>
<property name="default-height">300</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar" id="header_bar">
<child type="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="label">Hello, World!</property>
<property name="vexpand">true</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
</object>
</child>
</template>
<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 Musicus</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>