Skip to content

Commit

Permalink
GUI for GSettings (xou816#467)
Browse files Browse the repository at this point in the history
* Add settings window

* Remove unnecessary code

* Settings changes are reflected on startup

* Reflect changes on selecting a theme or closing the preferences window

* Fix UI issues in settings window

* Player stops when its settings changes

* Add missing comment for translators

* Improve UI for preferences

* Remove unnecessary code

* Put settings in the state

* Remove redundant action

* Fix unused imports

* Remove extra cloning

* Add paths for settings to meson.build
  • Loading branch information
sei0o authored Feb 28, 2022
1 parent 2ac769f commit ef3309c
Show file tree
Hide file tree
Showing 16 changed files with 496 additions and 13 deletions.
3 changes: 3 additions & 0 deletions src/app/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub use playlist::*;
mod login;
pub use login::*;

mod settings;
pub use settings::*;

mod player_notifier;
pub use player_notifier::PlayerNotifier;

Expand Down
5 changes: 4 additions & 1 deletion src/app/components/player_notifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use futures::channel::mpsc::UnboundedSender;
use librespot::core::spotify_id::SpotifyId;

use crate::app::components::EventListener;
use crate::app::state::{LoginAction, LoginEvent, LoginStartedEvent, PlaybackEvent};
use crate::app::state::{LoginAction, LoginEvent, LoginStartedEvent, PlaybackEvent, SettingsEvent};
use crate::app::{AppAction, AppEvent};
use crate::player::Command;

Expand Down Expand Up @@ -54,6 +54,9 @@ impl EventListener for PlayerNotifier {
}),
AppEvent::LoginEvent(LoginEvent::FreshTokenRequested) => Some(Command::RefreshToken),
AppEvent::LoginEvent(LoginEvent::LogoutCompleted) => Some(Command::Logout),
AppEvent::SettingsEvent(SettingsEvent::PlayerSettingsChanged) => {
Some(Command::ReloadSettings)
}
_ => None,
};

Expand Down
5 changes: 5 additions & 0 deletions src/app/components/settings/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod settings;
mod settings_model;

pub use settings::*;
pub use settings_model::*;
250 changes: 250 additions & 0 deletions src/app/components/settings/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
use crate::app::components::EventListener;
use crate::app::AppEvent;
use crate::settings::SpotSettings;

use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
use libadwaita::prelude::*;

use super::SettingsModel;

const SETTINGS: &str = "dev.alextren.Spot";

mod imp {

use super::*;
use libadwaita::subclass::prelude::*;

#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/dev/alextren/Spot/components/settings.ui")]
pub struct SettingsWindow {
#[template_child]
pub player_bitrate: TemplateChild<libadwaita::ComboRow>,

#[template_child]
pub alsa_device: TemplateChild<gtk::Entry>,

#[template_child]
pub alsa_device_row: TemplateChild<libadwaita::ActionRow>,

#[template_child]
pub audio_backend: TemplateChild<libadwaita::ComboRow>,

#[template_child]
pub ap_port: TemplateChild<gtk::Entry>,

#[template_child]
pub theme: TemplateChild<libadwaita::ComboRow>,
}

#[glib::object_subclass]
impl ObjectSubclass for SettingsWindow {
const NAME: &'static str = "SettingsWindow";
type Type = super::SettingsWindow;
type ParentType = libadwaita::PreferencesWindow;

fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}

fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}

impl ObjectImpl for SettingsWindow {}
impl WidgetImpl for SettingsWindow {}
impl WindowImpl for SettingsWindow {}
impl AdwWindowImpl for SettingsWindow {}
impl PreferencesWindowImpl for SettingsWindow {}
}

glib::wrapper! {
pub struct SettingsWindow(ObjectSubclass<imp::SettingsWindow>) @extends gtk::Widget, gtk::Window, libadwaita::Window, libadwaita::PreferencesWindow;
}

impl SettingsWindow {
pub fn new() -> Self {
let window: Self =
glib::Object::new(&[]).expect("Failed to create an instance of SettingsWindow");

window.bind_backend_and_device();
window.bind_settings();
window.connect_theme_select();
window
}

fn bind_backend_and_device(&self) {
let widget = imp::SettingsWindow::from_instance(self);

let audio_backend = widget
.audio_backend
.downcast_ref::<libadwaita::ComboRow>()
.unwrap();
let alsa_device_row = widget
.alsa_device_row
.downcast_ref::<libadwaita::ActionRow>()
.unwrap();

audio_backend
.bind_property("selected", alsa_device_row, "visible")
.transform_to(|_, value| value.get::<u32>().ok().map(|u| (u == 1).to_value()))
.build();

if audio_backend.selected() == 0 {
alsa_device_row.set_visible(false);
}
}

fn bind_settings(&self) {
let widget = imp::SettingsWindow::from_instance(self);
let settings = gio::Settings::new(SETTINGS);

let player_bitrate = widget
.player_bitrate
.downcast_ref::<libadwaita::ComboRow>()
.unwrap();
settings
.bind("player-bitrate", player_bitrate, "selected")
.mapping(|variant, _| {
variant.str().map(|s| {
match s {
"96" => 0,
"160" => 1,
"320" => 2,
_ => unreachable!(),
}
.to_value()
})
})
.set_mapping(|value, _| {
value.get::<u32>().ok().map(|u| {
match u {
0 => "96",
1 => "160",
2 => "320",
_ => unreachable!(),
}
.to_variant()
})
})
.build();

let alsa_device = widget.alsa_device.downcast_ref::<gtk::Entry>().unwrap();
settings.bind("alsa-device", alsa_device, "text").build();

let audio_backend = widget
.audio_backend
.downcast_ref::<libadwaita::ComboRow>()
.unwrap();
settings
.bind("audio-backend", audio_backend, "selected")
.mapping(|variant, _| {
variant.str().map(|s| {
match s {
"pulseaudio" => 0,
"alsa" => 1,
_ => unreachable!(),
}
.to_value()
})
})
.set_mapping(|value, _| {
value.get::<u32>().ok().map(|u| {
match u {
0 => "pulseaudio",
1 => "alsa",
_ => unreachable!(),
}
.to_variant()
})
})
.build();

let ap_port = widget.ap_port.downcast_ref::<gtk::Entry>().unwrap();
settings
.bind("ap-port", ap_port, "text")
.mapping(|variant, _| variant.get::<u32>().map(|s| s.to_value()))
.set_mapping(|value, _| value.get::<u32>().ok().map(|u| u.to_variant()))
.build();

let theme = widget.theme.downcast_ref::<libadwaita::ComboRow>().unwrap();
settings
.bind("prefers-dark-theme", theme, "selected")
.mapping(|variant, _| {
variant
.get::<bool>()
.map(|prefer_dark| if prefer_dark { 1 } else { 0 }.to_value())
})
.set_mapping(|value, _| value.get::<u32>().ok().map(|u| (u == 1).to_variant()))
.build();
}

fn connect_theme_select(&self) {
let widget = imp::SettingsWindow::from_instance(self);
let theme = widget.theme.downcast_ref::<libadwaita::ComboRow>().unwrap();
theme.connect_selected_notify(|theme| {
let prefers_dark_theme = theme.selected() == 1;
let manager = libadwaita::StyleManager::default();

manager.set_color_scheme(if prefers_dark_theme {
libadwaita::ColorScheme::PreferDark
} else {
libadwaita::ColorScheme::PreferLight
});
});
}

fn connect_close<F>(&self, on_close: F)
where
F: Fn() + 'static,
{
let window = self.upcast_ref::<libadwaita::Window>();

window.connect_close_request(
clone!(@weak self as _self => @default-return gtk::Inhibit(false), move |_| {
on_close();
gtk::Inhibit(false)
}),
);
}
}

pub struct Settings {
parent: gtk::Window,
settings_window: SettingsWindow,
}

impl Settings {
pub fn new(parent: gtk::Window, model: SettingsModel) -> Self {
let settings_window = SettingsWindow::new();

settings_window.connect_close(move || {
let new_settings = SpotSettings::new_from_gsettings().unwrap_or_default();
if model.settings().player_settings != new_settings.player_settings {
model.stop_player();
}
model.set_settings();
});

Self {
parent,
settings_window,
}
}

fn window(&self) -> &libadwaita::Window {
self.settings_window.upcast_ref::<libadwaita::Window>()
}

pub fn show_self(&self) {
self.window().set_transient_for(Some(&self.parent));
self.window().set_modal(true);
self.window().show();
}
}

impl EventListener for Settings {
fn on_event(&mut self, _: &AppEvent) {}
}
91 changes: 91 additions & 0 deletions src/app/components/settings/settings.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0" />
<requires lib="libadwaita" version="1.0" />
<template class="SettingsWindow" parent="AdwPreferencesWindow">
<property name="default-width">600</property>
<property name="hide-on-close">1</property>
<property name="search-enabled">0</property>
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes" comments="Header for a group of preference items regarding audio">Audio</property>
<child>
<object class="AdwComboRow" id="audio_backend">
<property name="title" translatable="yes" comments="Title for an item in preferences">Audio Backend</property>
<property name="model">
<object class="GtkStringList">
<items>
<item>PulseAudio</item>
<item>ALSA</item>
</items>
</object>
</property>
</object>
</child>
<child>
<object class="AdwActionRow" id="alsa_device_row">
<property name="title" translatable="yes" comments="Title for an item in preferences">ALSA Device</property>
<property name="subtitle" translatable="yes" comments="Description for the item (ALSA Device) in preferences">Applied only if audio backend is ALSA</property>
<child>
<object class="GtkEntry" id="alsa_device">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwComboRow" id="player_bitrate">
<property name="title" translatable="yes" comments="Title for an item in preferences">Audio Quality</property>
<property name="model">
<object class="GtkStringList">
<items>
<item translatable="yes">Normal</item>
<item translatable="yes">High</item>
<item translatable="yes">Very high</item>
</items>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes" comments="Header for a group of preference items regarding the application's appearance">Appearance</property>
<child>
<object class="AdwComboRow" id="theme">
<property name="title" translatable="yes" comments="Title for an item in preferences">Theme</property>
<property name="model">
<object class="GtkStringList">
<items>
<item translatable="yes">Light</item>
<item translatable="yes">Dark</item>
</items>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes" comments="Header for a group of preference items regarding network">Network</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes" comments="Title for an item in preferences">Access Point Port</property>
<property name="subtitle" translatable="yes" comments="Longer description for an item (Access Point Port) in preferences">Port used for connections to Spotify's Access Point. Set to 0 if any port is fine.</property>
<child>
<object class="GtkEntry" id="ap_port">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
Loading

0 comments on commit ef3309c

Please sign in to comment.