Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions psst-gui/src/controller/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use druid::{
};

use crate::cmd;
use druid::widget::ValueTextBox;

pub struct InputController {
on_submit: Option<Box<dyn Fn(&mut EventCtx, &mut String, &Env)>>,
Expand Down Expand Up @@ -32,6 +33,32 @@ impl Controller<String, TextBox<String>> for InputController {
event: &Event,
data: &mut String,
env: &Env,
) {
self.match_event(child, ctx, event, data, env)
}
}

impl Controller<String, ValueTextBox<String>> for InputController {
fn event(
&mut self,
child: &mut ValueTextBox<String>,
ctx: &mut EventCtx,
event: &Event,
data: &mut String,
env: &Env,
) {
self.match_event(child, ctx, event, data, env)
}
}

impl InputController {
fn match_event(
&mut self,
child: &mut impl Widget<String>,
ctx: &mut EventCtx,
event: &Event,
data: &mut String,
env: &Env,
) {
match event {
Event::Command(cmd) if cmd.is(cmd::SET_FOCUS) => {
Expand Down
2 changes: 2 additions & 0 deletions psst-gui/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod on_cmd;
mod on_cmd_async;
mod playback;
mod session;
mod shortcut_formatter;

pub use debounce::Debounce;
pub use ex_click::ExClick;
Expand All @@ -15,3 +16,4 @@ pub use on_cmd::OnCmd;
pub use on_cmd_async::OnCmdAsync;
pub use playback::PlaybackController;
pub use session::SessionController;
pub use shortcut_formatter::ShortcutFormatter;
13 changes: 7 additions & 6 deletions psst-gui/src/controller/playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crossbeam_channel::Sender;
use druid::{
im::Vector,
widget::{prelude::*, Controller},
Code, ExtEventSink, InternalLifeCycle, KbKey, WindowHandle,
ExtEventSink, InternalLifeCycle, WindowHandle,
};
use psst_core::{
audio_normalize::NormalizationLevel,
Expand All @@ -21,6 +21,7 @@ use souvlaki::{
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback, MediaPosition, PlatformConfig,
};

use crate::data::matches;
use crate::{
cmd,
data::{
Expand Down Expand Up @@ -394,23 +395,23 @@ where
ctx.set_handled();
}
//
Event::KeyDown(key) if key.code == Code::Space => {
Event::KeyDown(key) if matches(key, &data.config.shortcuts.play_resume) => {
self.pause_or_resume();
ctx.set_handled();
}
Event::KeyDown(key) if key.code == Code::ArrowRight => {
Event::KeyDown(key) if matches(key, &data.config.shortcuts.next_song) => {
self.next();
ctx.set_handled();
}
Event::KeyDown(key) if key.code == Code::ArrowLeft => {
Event::KeyDown(key) if matches(key, &data.config.shortcuts.previous_song) => {
self.previous();
ctx.set_handled();
}
Event::KeyDown(key) if key.key == KbKey::Character("+".to_string()) => {
Event::KeyDown(key) if matches(key, &data.config.shortcuts.volume_increase) => {
data.playback.volume = (data.playback.volume + 0.1).min(1.0);
ctx.set_handled();
}
Event::KeyDown(key) if key.key == KbKey::Character("-".to_string()) => {
Event::KeyDown(key) if matches(key, &data.config.shortcuts.volume_decrease) => {
data.playback.volume = (data.playback.volume - 0.1).max(0.0);
ctx.set_handled();
}
Expand Down
30 changes: 30 additions & 0 deletions psst-gui/src/controller/shortcut_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::data::KbShortcut;
use druid::text::{Formatter, Selection, Validation, ValidationError};
use std::str::FromStr;

pub struct ShortcutFormatter;

impl Formatter<String> for ShortcutFormatter {
fn format(&self, value: &String) -> String {
value.to_string()
}

fn validate_partial_input(&self, input: &str, _: &Selection) -> Validation {
match KbShortcut::from_str(input) {
Ok(_) => Validation::success(),
Err(_) => Validation::failure(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid Shortcut",
)),
}
}

fn value(&self, input: &str) -> Result<String, ValidationError> {
Ok(KbShortcut::from_str(input)
.or(Err(ValidationError::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid Shortcut",
))))?
.to_string())
}
}
57 changes: 57 additions & 0 deletions psst-gui/src/data/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use psst_core::{
use serde::{Deserialize, Serialize};

use super::Promise;
use druid::lens::Field;

#[derive(Clone, Debug, Data, Lens)]
pub struct Preferences {
Expand All @@ -34,6 +35,7 @@ impl Preferences {
pub enum PreferencesTab {
General,
Cache,
Shortcuts,
}

#[derive(Clone, Debug, Data, Lens)]
Expand Down Expand Up @@ -72,6 +74,7 @@ pub struct Config {
pub audio_quality: AudioQuality,
pub theme: Theme,
pub volume: f64,
pub shortcuts: KbShortcuts,
}

impl Default for Config {
Expand All @@ -81,6 +84,7 @@ impl Default for Config {
audio_quality: Default::default(),
theme: Default::default(),
volume: 1.0,
shortcuts: Default::default(),
}
}
}
Expand Down Expand Up @@ -193,3 +197,56 @@ impl Default for Theme {
Self::Light
}
}

#[derive(Clone, Debug, Eq, PartialEq, Data, Lens, Serialize, Deserialize)]
pub struct KbShortcuts {
pub play_resume: String,
pub volume_increase: String,
pub volume_decrease: String,
pub next_song: String,
pub previous_song: String,
}

impl KbShortcuts {
pub fn to_desc_with_lens(
&self,
) -> Vec<(
Field<fn(&KbShortcuts) -> &String, fn(&mut KbShortcuts) -> &mut String>,
String,
)> {
vec![
(
druid::lens!(KbShortcuts, play_resume),
"Play/Resume".to_string(),
),
(
druid::lens!(KbShortcuts, volume_increase),
"Volume Increase".to_string(),
),
(
druid::lens!(KbShortcuts, volume_decrease),
"Volume Decrease".to_string(),
),
(
druid::lens!(KbShortcuts, next_song),
"Next Song".to_string(),
),
(
druid::lens!(KbShortcuts, previous_song),
"Previous Song".to_string(),
),
]
}
}

impl Default for KbShortcuts {
fn default() -> Self {
Self {
play_resume: "Space".to_string(),
volume_increase: "+".to_string(),
volume_decrease: "-".to_string(),
next_song: "ArrowRight".to_string(),
previous_song: "ArrowLeft".to_string(),
}
}
}
90 changes: 90 additions & 0 deletions psst-gui/src/data/kbshortcut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use druid::Data;
use druid_shell::{Code, KbKey, KeyEvent};
use std::fmt;
use std::str::FromStr;

#[derive(Clone, Debug, Eq, PartialEq)]
/// Keyboard Shortcut comprised of either a [`KbKey`] or a [`Code`]
pub enum KbShortcut {
Key(KbKey),
Code(Code),
}

impl Data for KbShortcut {
fn same(&self, other: &Self) -> bool {
self == other
}
}

impl KbShortcut {
/// Return whether a given [`KeyEvent`] matches this shortcut
pub fn matches(&self, event: &KeyEvent) -> bool {
match self {
KbShortcut::Key(key) => &event.key == key,
KbShortcut::Code(code) => &event.code == code,
}
}
}

/// Given a [`&str`], return the corresponding [`Code`] or an `Err` if there is no corresponding
/// code.
/// TODO: Add more String-to-Code mappings
fn kb_code_from_str(s: &str) -> Result<Code, ()> {
match s {
"NumpadAdd" => Ok(Code::NumpadAdd),
"Minus" => Ok(Code::Minus),
"Space" => Ok(Code::Space),
"ArrowRight" => Ok(Code::ArrowRight),
"ArrowLeft" => Ok(Code::ArrowLeft),
"ArrowUp" => Ok(Code::ArrowUp),
"ArrowDown" => Ok(Code::ArrowDown),
"Backspace" => Ok(Code::Backspace),
"Enter" => Ok(Code::Enter),
"Tab" => Ok(Code::Tab),
_ => Err(()),
}
}

impl fmt::Display for KbShortcut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KbShortcut::Key(key) => {
write!(f, "{}", key.to_string())
}
KbShortcut::Code(code) => {
write!(f, "{}", code.to_string())
}
}
}
}

#[derive(Debug, Clone)]
pub struct ParseShortcutError;

impl fmt::Display for ParseShortcutError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error parsing shortcut!")
}
}

impl FromStr for KbShortcut {
type Err = ParseShortcutError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(code) = kb_code_from_str(s) {
Ok(KbShortcut::Code(code))
} else if s.len() == 1 {
Ok(KbShortcut::Key(KbKey::Character(s.to_string())))
} else {
Err(ParseShortcutError)
}
}
}

/// Return whether a given [`KeyEvent`] matches the code or character given in `str`
pub fn matches(key_event: &KeyEvent, str: &String) -> bool {
if let Ok(shortcut) = KbShortcut::from_str(&str) {
return shortcut.matches(key_event);
}
false
}
6 changes: 5 additions & 1 deletion psst-gui/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod artist;
mod config;
mod ctx;
mod id;
mod kbshortcut;
mod nav;
mod playback;
mod playlist;
Expand All @@ -24,8 +25,11 @@ use psst_core::session::SessionService;
pub use crate::data::{
album::{Album, AlbumDetail, AlbumLink, AlbumType, Copyright, CopyrightType},
artist::{Artist, ArtistAlbums, ArtistDetail, ArtistLink, ArtistTracks},
config::{AudioQuality, Authentication, Config, Preferences, PreferencesTab, Theme},
config::{
AudioQuality, Authentication, Config, KbShortcuts, Preferences, PreferencesTab, Theme,
},
ctx::Ctx,
kbshortcut::{matches, KbShortcut},
nav::{Nav, SpotifyUrl},
playback::{
NowPlaying, Playback, PlaybackOrigin, PlaybackPayload, PlaybackState, QueueBehavior,
Expand Down
3 changes: 2 additions & 1 deletion psst-gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn preferences_window() -> WindowDesc<AppState> {
let win = WindowDesc::new(preferences_widget())
.title("Preferences")
.window_size(win_size)
.resizable(false)
.resizable(true)
.show_title(false)
.transparent_titlebar(true);
if cfg!(target_os = "macos") {
Expand Down Expand Up @@ -80,6 +80,7 @@ fn root_widget() -> impl Widget<AppState> {
.with_child(sidebar_menu_widget())
.with_default_spacer()
.with_flex_child(playlists, 1.0)
.with_default_spacer()
.with_child(volume_slider())
.with_default_spacer()
.with_child(user::user_widget())
Expand Down
Loading