mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +01:00
add keybinds
This commit is contained in:
parent
7b7b9f9a92
commit
b22d9ffeb3
@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
collections::HashMap,
|
collections::{BTreeMap, HashMap},
|
||||||
io::Cursor,
|
io::Cursor,
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
sync::{mpsc::Sender, Arc, Mutex},
|
sync::{mpsc::Sender, Arc, Mutex},
|
||||||
@ -290,6 +290,8 @@ pub struct Gui {
|
|||||||
pub size: UVec2,
|
pub size: UVec2,
|
||||||
pub mouse_pos: Vec2,
|
pub mouse_pos: Vec2,
|
||||||
pub font: Font,
|
pub font: Font,
|
||||||
|
pub keybinds: BTreeMap<KeyBinding, KeyActionId>,
|
||||||
|
pub key_actions: KeyActions,
|
||||||
pub covers: Option<HashMap<CoverId, GuiServerImage>>,
|
pub covers: Option<HashMap<CoverId, GuiServerImage>>,
|
||||||
pub custom_images: Option<HashMap<String, GuiServerImage>>,
|
pub custom_images: Option<HashMap<String, GuiServerImage>>,
|
||||||
pub modifiers: ModifiersState,
|
pub modifiers: ModifiersState,
|
||||||
@ -448,6 +450,8 @@ impl Gui {
|
|||||||
size: UVec2::ZERO,
|
size: UVec2::ZERO,
|
||||||
mouse_pos: Vec2::ZERO,
|
mouse_pos: Vec2::ZERO,
|
||||||
font,
|
font,
|
||||||
|
keybinds: BTreeMap::new(),
|
||||||
|
key_actions: KeyActions::default(),
|
||||||
covers: Some(HashMap::new()),
|
covers: Some(HashMap::new()),
|
||||||
custom_images: Some(HashMap::new()),
|
custom_images: Some(HashMap::new()),
|
||||||
// font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(),
|
// font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(),
|
||||||
@ -465,6 +469,32 @@ impl Gui {
|
|||||||
frames_drawn: 0,
|
frames_drawn: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_specific_gui_elem_config(&mut self, elem: SpecificGuiElem) -> &mut GuiElemCfg {
|
||||||
|
match elem {
|
||||||
|
SpecificGuiElem::SearchArtist => self
|
||||||
|
.gui
|
||||||
|
.c_main_view
|
||||||
|
.children
|
||||||
|
.library_browser
|
||||||
|
.c_search_artist
|
||||||
|
.config_mut(),
|
||||||
|
SpecificGuiElem::SearchAlbum => self
|
||||||
|
.gui
|
||||||
|
.c_main_view
|
||||||
|
.children
|
||||||
|
.library_browser
|
||||||
|
.c_search_album
|
||||||
|
.config_mut(),
|
||||||
|
SpecificGuiElem::SearchSong => self
|
||||||
|
.gui
|
||||||
|
.c_main_view
|
||||||
|
.children
|
||||||
|
.library_browser
|
||||||
|
.c_search_song
|
||||||
|
.config_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the trait implemented by all Gui elements.
|
/// the trait implemented by all Gui elements.
|
||||||
@ -547,8 +577,14 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
|||||||
info.child_has_keyboard_focus = false;
|
info.child_has_keyboard_focus = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
info.mouse_pos_in_bounds = info.pos.contains(info.mouse_pos);
|
||||||
|
if !info.mouse_pos_in_bounds {
|
||||||
|
self.config_mut().mouse_down = (false, false, false);
|
||||||
|
}
|
||||||
// call trait's draw function
|
// call trait's draw function
|
||||||
self.draw(info, g);
|
self.draw(info, g);
|
||||||
|
// reset cfg
|
||||||
|
self.config_mut().init = false;
|
||||||
// reset info
|
// reset info
|
||||||
info.has_keyboard_focus = false;
|
info.has_keyboard_focus = false;
|
||||||
let focus_path = info.child_has_keyboard_focus;
|
let focus_path = info.child_has_keyboard_focus;
|
||||||
@ -626,9 +662,18 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
|||||||
&mut |v: &mut dyn GuiElem| {
|
&mut |v: &mut dyn GuiElem| {
|
||||||
if v.config().mouse_events {
|
if v.config().mouse_events {
|
||||||
match button {
|
match button {
|
||||||
MouseButton::Left => v.config_mut().mouse_down.0 = true,
|
MouseButton::Left => {
|
||||||
MouseButton::Middle => v.config_mut().mouse_down.1 = true,
|
v.config_mut().mouse_down.0 = true;
|
||||||
MouseButton::Right => v.config_mut().mouse_down.2 = true,
|
v.config_mut().mouse_pressed.0 = true;
|
||||||
|
}
|
||||||
|
MouseButton::Middle => {
|
||||||
|
v.config_mut().mouse_down.1 = true;
|
||||||
|
v.config_mut().mouse_pressed.1 = true;
|
||||||
|
}
|
||||||
|
MouseButton::Right => {
|
||||||
|
v.config_mut().mouse_down.2 = true;
|
||||||
|
v.config_mut().mouse_pressed.2 = true;
|
||||||
|
}
|
||||||
MouseButton::Other(_) => {}
|
MouseButton::Other(_) => {}
|
||||||
}
|
}
|
||||||
Some(v.mouse_down(button))
|
Some(v.mouse_down(button))
|
||||||
@ -660,9 +705,18 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
|||||||
self._recursive_all(&mut |v| {
|
self._recursive_all(&mut |v| {
|
||||||
if v.config().mouse_events {
|
if v.config().mouse_events {
|
||||||
match button {
|
match button {
|
||||||
MouseButton::Left => v.config_mut().mouse_down.0 = false,
|
MouseButton::Left => {
|
||||||
MouseButton::Middle => v.config_mut().mouse_down.1 = false,
|
v.config_mut().mouse_down.0 = false;
|
||||||
MouseButton::Right => v.config_mut().mouse_down.2 = false,
|
v.config_mut().mouse_pressed.0 = false;
|
||||||
|
}
|
||||||
|
MouseButton::Middle => {
|
||||||
|
v.config_mut().mouse_down.1 = false;
|
||||||
|
v.config_mut().mouse_pressed.1 = false;
|
||||||
|
}
|
||||||
|
MouseButton::Right => {
|
||||||
|
v.config_mut().mouse_down.2 = false;
|
||||||
|
v.config_mut().mouse_pressed.2 = false;
|
||||||
|
}
|
||||||
MouseButton::Other(_) => {}
|
MouseButton::Other(_) => {}
|
||||||
}
|
}
|
||||||
vec.extend(v.mouse_up(button));
|
vec.extend(v.mouse_up(button));
|
||||||
@ -947,6 +1001,9 @@ pub struct GuiElemCfg {
|
|||||||
/// if true, indicates that something (text size, screen size, ...) has changed
|
/// if true, indicates that something (text size, screen size, ...) has changed
|
||||||
/// and you should probably relayout and redraw from scratch.
|
/// and you should probably relayout and redraw from scratch.
|
||||||
pub redraw: bool,
|
pub redraw: bool,
|
||||||
|
/// will be set to false after `draw`.
|
||||||
|
/// can be used to, for example, add the keybinds for your element.
|
||||||
|
pub init: bool,
|
||||||
/// Position relative to the parent where this element should be drawn.
|
/// Position relative to the parent where this element should be drawn.
|
||||||
/// ((0, 0), (1, 1)) is the default and fills all available space.
|
/// ((0, 0), (1, 1)) is the default and fills all available space.
|
||||||
/// ((0, 0.5), (0.5, 1)) fills the bottom left quarter.
|
/// ((0, 0.5), (0.5, 1)) fills the bottom left quarter.
|
||||||
@ -955,8 +1012,10 @@ pub struct GuiElemCfg {
|
|||||||
/// in draw, use info.pos instead, as pixel_pos is only updated *after* draw().
|
/// in draw, use info.pos instead, as pixel_pos is only updated *after* draw().
|
||||||
/// this can act like a "previous pos" field within draw.
|
/// this can act like a "previous pos" field within draw.
|
||||||
pub pixel_pos: Rectangle,
|
pub pixel_pos: Rectangle,
|
||||||
/// which mouse buttons were pressed down while the mouse was on this element and haven't been released since? (Left/Middle/Right)
|
/// which mouse buttons were pressed down while the mouse was on this element and haven't been released OR moved away since? (Left/Middle/Right)
|
||||||
pub mouse_down: (bool, bool, bool),
|
pub mouse_down: (bool, bool, bool),
|
||||||
|
/// which mouse buttons were pressed down while the mouse was on this element and haven't been released since? (Left/Middle/Right)
|
||||||
|
pub mouse_pressed: (bool, bool, bool),
|
||||||
/// Set this to true to receive mouse click events when the mouse is within this element's bounds
|
/// Set this to true to receive mouse click events when the mouse is within this element's bounds
|
||||||
pub mouse_events: bool,
|
pub mouse_events: bool,
|
||||||
/// Set this to true to receive scroll events when the mouse is within this element's bounds
|
/// Set this to true to receive scroll events when the mouse is within this element's bounds
|
||||||
@ -1015,9 +1074,11 @@ impl Default for GuiElemCfg {
|
|||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
redraw: false,
|
redraw: false,
|
||||||
|
init: true,
|
||||||
pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)),
|
pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)),
|
||||||
pixel_pos: Rectangle::ZERO,
|
pixel_pos: Rectangle::ZERO,
|
||||||
mouse_down: (false, false, false),
|
mouse_down: (false, false, false),
|
||||||
|
mouse_pressed: (false, false, false),
|
||||||
mouse_events: false,
|
mouse_events: false,
|
||||||
scroll_events: false,
|
scroll_events: false,
|
||||||
keyboard_events_watch: false,
|
keyboard_events_watch: false,
|
||||||
@ -1031,6 +1092,11 @@ impl Default for GuiElemCfg {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub enum GuiAction {
|
pub enum GuiAction {
|
||||||
OpenMain,
|
OpenMain,
|
||||||
|
/// Add a key action (and optionally bind it) and then call the given function
|
||||||
|
AddKeybind(Option<KeyBinding>, KeyAction, Box<dyn FnOnce(KeyActionId)>),
|
||||||
|
/// Binds the action to the keybinding, or unbinds it entirely
|
||||||
|
SetKeybind(KeyActionId, Option<KeyBinding>),
|
||||||
|
ForceIdle,
|
||||||
/// false -> prevent idling, true -> end idling even if already idle
|
/// false -> prevent idling, true -> end idling even if already idle
|
||||||
EndIdle(bool),
|
EndIdle(bool),
|
||||||
SetHighPerformance(bool),
|
SetHighPerformance(bool),
|
||||||
@ -1042,6 +1108,7 @@ pub enum GuiAction {
|
|||||||
ContextMenu(Option<(Vec<Box<dyn GuiElem>>)>),
|
ContextMenu(Option<(Vec<Box<dyn GuiElem>>)>),
|
||||||
/// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one.
|
/// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one.
|
||||||
ResetKeyboardFocus,
|
ResetKeyboardFocus,
|
||||||
|
SetFocused(SpecificGuiElem),
|
||||||
SetDragging(
|
SetDragging(
|
||||||
Option<(
|
Option<(
|
||||||
Dragging,
|
Dragging,
|
||||||
@ -1064,6 +1131,11 @@ pub enum Dragging {
|
|||||||
Queue(Queue),
|
Queue(Queue),
|
||||||
Queues(Vec<Queue>),
|
Queues(Vec<Queue>),
|
||||||
}
|
}
|
||||||
|
pub enum SpecificGuiElem {
|
||||||
|
SearchArtist,
|
||||||
|
SearchAlbum,
|
||||||
|
SearchSong,
|
||||||
|
}
|
||||||
|
|
||||||
/// GuiElems have access to this within draw.
|
/// GuiElems have access to this within draw.
|
||||||
/// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically.
|
/// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically.
|
||||||
@ -1076,6 +1148,8 @@ pub struct DrawInfo<'a> {
|
|||||||
/// absolute position of the mouse on the screen.
|
/// absolute position of the mouse on the screen.
|
||||||
/// compare this to `pos` to find the mouse's relative position.
|
/// compare this to `pos` to find the mouse's relative position.
|
||||||
pub mouse_pos: Vec2,
|
pub mouse_pos: Vec2,
|
||||||
|
/// true if `info.pos.contains(info.mouse_pos)`.
|
||||||
|
pub mouse_pos_in_bounds: bool,
|
||||||
pub helper: Option<&'a mut WindowHelper<GuiEvent>>,
|
pub helper: Option<&'a mut WindowHelper<GuiEvent>>,
|
||||||
pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
|
pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
|
||||||
pub covers: &'a mut HashMap<CoverId, GuiServerImage>,
|
pub covers: &'a mut HashMap<CoverId, GuiServerImage>,
|
||||||
@ -1114,6 +1188,34 @@ impl Gui {
|
|||||||
self.exec_gui_action(action);
|
self.exec_gui_action(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GuiAction::AddKeybind(bind, action, func) => {
|
||||||
|
let id = self.key_actions.add(action);
|
||||||
|
if let Some(bind) = bind {
|
||||||
|
self.keybinds.insert(bind, id);
|
||||||
|
}
|
||||||
|
func(id);
|
||||||
|
}
|
||||||
|
GuiAction::SetKeybind(action, bind) => {
|
||||||
|
for b in self
|
||||||
|
.keybinds
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(b, a)| {
|
||||||
|
if a.get_index() == action.get_index() {
|
||||||
|
Some(b)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
{
|
||||||
|
self.keybinds.remove(&b);
|
||||||
|
}
|
||||||
|
if let Some(bind) = bind {
|
||||||
|
eprintln!("Setting keybind: {:b} {:?}", bind.modifiers, bind.key);
|
||||||
|
self.keybinds.insert(bind, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
GuiAction::SendToServer(cmd) => {
|
GuiAction::SendToServer(cmd) => {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
eprintln!("[DEBUG] Sending command to server: {cmd:?}");
|
eprintln!("[DEBUG] Sending command to server: {cmd:?}");
|
||||||
@ -1122,6 +1224,11 @@ impl Gui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
GuiAction::ShowNotification(func) => _ = self.notif_sender.send(func),
|
GuiAction::ShowNotification(func) => _ = self.notif_sender.send(func),
|
||||||
|
GuiAction::SetFocused(elem) => {
|
||||||
|
self.get_specific_gui_elem_config(elem)
|
||||||
|
.request_keyboard_focus = true;
|
||||||
|
self.gui._keyboard_reset_focus();
|
||||||
|
}
|
||||||
GuiAction::ResetKeyboardFocus => _ = self.gui._keyboard_reset_focus(),
|
GuiAction::ResetKeyboardFocus => _ = self.gui._keyboard_reset_focus(),
|
||||||
GuiAction::SetDragging(d) => self.dragging = d,
|
GuiAction::SetDragging(d) => self.dragging = d,
|
||||||
GuiAction::SetHighPerformance(d) => self.high_performance = d,
|
GuiAction::SetHighPerformance(d) => self.high_performance = d,
|
||||||
@ -1172,6 +1279,9 @@ impl Gui {
|
|||||||
}
|
}
|
||||||
GuiAction::Do(f) => f(self),
|
GuiAction::Do(f) => f(self),
|
||||||
GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit),
|
GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit),
|
||||||
|
GuiAction::ForceIdle => {
|
||||||
|
self.gui.force_idle();
|
||||||
|
}
|
||||||
GuiAction::EndIdle(v) => {
|
GuiAction::EndIdle(v) => {
|
||||||
if v {
|
if v {
|
||||||
self.gui.unidle();
|
self.gui.unidle();
|
||||||
@ -1219,6 +1329,7 @@ impl WindowHandler<GuiEvent> for Gui {
|
|||||||
database: &mut *dblock,
|
database: &mut *dblock,
|
||||||
font: &self.font,
|
font: &self.font,
|
||||||
mouse_pos: self.mouse_pos,
|
mouse_pos: self.mouse_pos,
|
||||||
|
mouse_pos_in_bounds: false,
|
||||||
get_con: Arc::clone(&self.get_con),
|
get_con: Arc::clone(&self.get_con),
|
||||||
covers: &mut covers,
|
covers: &mut covers,
|
||||||
custom_images: &mut custom_images,
|
custom_images: &mut custom_images,
|
||||||
@ -1388,6 +1499,17 @@ impl WindowHandler<GuiEvent> for Gui {
|
|||||||
scancode: KeyScancode,
|
scancode: KeyScancode,
|
||||||
) {
|
) {
|
||||||
helper.request_redraw();
|
helper.request_redraw();
|
||||||
|
// handle keybinds unless settings are open, opening or closing
|
||||||
|
if self.gui.settings.0 == false && self.gui.settings.1.is_none() {
|
||||||
|
if let Some(key) = virtual_key_code {
|
||||||
|
let keybind = KeyBinding::new(&self.modifiers, key);
|
||||||
|
if let Some(action) = self.keybinds.get(&keybind) {
|
||||||
|
for a in self.key_actions.get(action).execute() {
|
||||||
|
self.exec_gui_action(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(VirtualKeyCode::Tab) = virtual_key_code {
|
if let Some(VirtualKeyCode::Tab) = virtual_key_code {
|
||||||
if !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) {
|
if !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) {
|
||||||
self.gui._keyboard_move_focus(self.modifiers.shift(), false);
|
self.gui._keyboard_move_focus(self.modifiers.shift(), false);
|
||||||
@ -1576,6 +1698,92 @@ impl GuiServerImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
|
pub struct KeyBinding {
|
||||||
|
pub modifiers: u8,
|
||||||
|
pub key: VirtualKeyCode,
|
||||||
|
}
|
||||||
|
impl KeyBinding {
|
||||||
|
const CTRL: u8 = 1;
|
||||||
|
const ALT: u8 = 2;
|
||||||
|
const SHIFT: u8 = 4;
|
||||||
|
const META: u8 = 8;
|
||||||
|
pub fn new(modifiers: &ModifiersState, key: VirtualKeyCode) -> Self {
|
||||||
|
Self {
|
||||||
|
modifiers: if modifiers.ctrl() { Self::CTRL } else { 0 }
|
||||||
|
| if modifiers.alt() { Self::ALT } else { 0 }
|
||||||
|
| if modifiers.shift() { Self::SHIFT } else { 0 }
|
||||||
|
| if modifiers.logo() { Self::META } else { 0 },
|
||||||
|
key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ctrl(key: VirtualKeyCode) -> Self {
|
||||||
|
Self {
|
||||||
|
modifiers: Self::CTRL,
|
||||||
|
key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ctrl_shift(key: VirtualKeyCode) -> Self {
|
||||||
|
Self {
|
||||||
|
modifiers: Self::CTRL | Self::SHIFT,
|
||||||
|
key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_ctrl(&self) -> bool {
|
||||||
|
self.modifiers & Self::CTRL != 0
|
||||||
|
}
|
||||||
|
pub fn get_alt(&self) -> bool {
|
||||||
|
self.modifiers & Self::ALT != 0
|
||||||
|
}
|
||||||
|
pub fn get_shift(&self) -> bool {
|
||||||
|
self.modifiers & Self::SHIFT != 0
|
||||||
|
}
|
||||||
|
pub fn get_meta(&self) -> bool {
|
||||||
|
self.modifiers & Self::META != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct KeyAction {
|
||||||
|
pub category: String,
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub action: Box<dyn FnMut() -> Vec<GuiAction>>,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
impl KeyAction {
|
||||||
|
pub fn execute(&mut self) -> Vec<GuiAction> {
|
||||||
|
if self.enabled {
|
||||||
|
(self.action)()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct KeyActions(Vec<KeyAction>);
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct KeyActionId(usize);
|
||||||
|
impl KeyActionId {
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl KeyActions {
|
||||||
|
pub fn add(&mut self, action: KeyAction) -> KeyActionId {
|
||||||
|
let id = KeyActionId(self.0.len());
|
||||||
|
self.0.push(action);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
pub fn get(&mut self, action: &KeyActionId) -> &mut KeyAction {
|
||||||
|
&mut self.0[action.0]
|
||||||
|
}
|
||||||
|
pub fn view(&self) -> &[KeyAction] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (KeyActionId, &KeyAction)> {
|
||||||
|
self.0.iter().enumerate().map(|(i, v)| (KeyActionId(i), v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn morph_rect(a: &Rectangle, b: &Rectangle, p: f32) -> Rectangle {
|
pub fn morph_rect(a: &Rectangle, b: &Rectangle, p: f32) -> Rectangle {
|
||||||
let q = 1.0 - p;
|
let q = 1.0 - p;
|
||||||
Rectangle::from_tuples(
|
Rectangle::from_tuples(
|
||||||
|
@ -611,7 +611,7 @@ impl GuiElem for Slider {
|
|||||||
);
|
);
|
||||||
let line_left = line_pos.top_left().x;
|
let line_left = line_pos.top_left().x;
|
||||||
let line_width = line_pos.width();
|
let line_width = line_pos.width();
|
||||||
if self.config.mouse_down.0 {
|
if self.config.mouse_pressed.0 {
|
||||||
self.val = self.min
|
self.val = self.min
|
||||||
+ (self.max - self.min)
|
+ (self.max - self.min)
|
||||||
* 1.0f64.min(0.0f64.max(
|
* 1.0f64.min(0.0f64.max(
|
||||||
|
@ -46,13 +46,13 @@ with Regex search and drag-n-drop.
|
|||||||
|
|
||||||
pub struct LibraryBrowser {
|
pub struct LibraryBrowser {
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
c_search_artist: TextField,
|
pub c_search_artist: TextField,
|
||||||
c_search_album: TextField,
|
pub c_search_album: TextField,
|
||||||
c_search_song: TextField,
|
pub c_search_song: TextField,
|
||||||
c_scroll_box: ScrollBox<Vec<ListElement>>,
|
pub c_scroll_box: ScrollBox<Vec<ListElement>>,
|
||||||
c_filter_button: Button<[Label; 1]>,
|
pub c_filter_button: Button<[Label; 1]>,
|
||||||
c_filter_panel: FilterPanel,
|
pub c_filter_panel: FilterPanel,
|
||||||
c_selected_counter_panel: Panel<[Label; 1]>,
|
pub c_selected_counter_panel: Panel<[Label; 1]>,
|
||||||
// - - -
|
// - - -
|
||||||
library_sorted: Vec<(ArtistId, Vec<SongId>, Vec<(AlbumId, Vec<SongId>)>)>,
|
library_sorted: Vec<(ArtistId, Vec<SongId>, Vec<(AlbumId, Vec<SongId>)>)>,
|
||||||
library_filtered: Vec<(
|
library_filtered: Vec<(
|
||||||
|
@ -4,7 +4,10 @@ use musicdb_lib::{data::queue::QueueContent, server::Command};
|
|||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
gui::{
|
||||||
|
DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren, KeyAction, KeyBinding,
|
||||||
|
SpecificGuiElem,
|
||||||
|
},
|
||||||
gui_anim::AnimationController,
|
gui_anim::AnimationController,
|
||||||
gui_base::{Button, Panel},
|
gui_base::{Button, Panel},
|
||||||
gui_edit_song::EditorForSongs,
|
gui_edit_song::EditorForSongs,
|
||||||
@ -271,7 +274,10 @@ impl GuiElem for GuiScreen {
|
|||||||
key: Option<speedy2d::window::VirtualKeyCode>,
|
key: Option<speedy2d::window::VirtualKeyCode>,
|
||||||
_scan: speedy2d::window::KeyScancode,
|
_scan: speedy2d::window::KeyScancode,
|
||||||
) -> Vec<GuiAction> {
|
) -> Vec<GuiAction> {
|
||||||
|
if down {
|
||||||
|
// on releasing Ctrl in Ctrl+I => Idle keybind, don't unidle
|
||||||
self.not_idle();
|
self.not_idle();
|
||||||
|
}
|
||||||
if self.hotkey.triggered(modifiers, down, key) {
|
if self.hotkey.triggered(modifiers, down, key) {
|
||||||
self.config.request_keyboard_focus = true;
|
self.config.request_keyboard_focus = true;
|
||||||
vec![GuiAction::ResetKeyboardFocus]
|
vec![GuiAction::ResetKeyboardFocus]
|
||||||
@ -284,6 +290,45 @@ impl GuiElem for GuiScreen {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
|
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
|
||||||
|
if self.config.init {
|
||||||
|
info.actions.extend([
|
||||||
|
GuiAction::AddKeybind(
|
||||||
|
Some(KeyBinding::ctrl(VirtualKeyCode::Q)),
|
||||||
|
KeyAction {
|
||||||
|
category: "General".to_owned(),
|
||||||
|
title: "Quit".to_owned(),
|
||||||
|
description: "Closes the application".to_owned(),
|
||||||
|
action: Box::new(|| vec![GuiAction::Exit]),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
Box::new(|_| {}),
|
||||||
|
),
|
||||||
|
GuiAction::AddKeybind(
|
||||||
|
Some(KeyBinding::ctrl(VirtualKeyCode::I)),
|
||||||
|
KeyAction {
|
||||||
|
category: "General".to_owned(),
|
||||||
|
title: "Idle".to_owned(),
|
||||||
|
description: "Opens the idle display".to_owned(),
|
||||||
|
action: Box::new(|| vec![GuiAction::ForceIdle]),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
Box::new(|_| {}),
|
||||||
|
),
|
||||||
|
GuiAction::AddKeybind(
|
||||||
|
Some(KeyBinding::ctrl(VirtualKeyCode::F)),
|
||||||
|
KeyAction {
|
||||||
|
category: "Library".to_owned(),
|
||||||
|
title: "Search songs".to_owned(),
|
||||||
|
description: "moves keyboard focus to the song search".to_owned(),
|
||||||
|
action: Box::new(|| {
|
||||||
|
vec![GuiAction::SetFocused(SpecificGuiElem::SearchSong)]
|
||||||
|
}),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
Box::new(|_| {}),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
// idle stuff
|
// idle stuff
|
||||||
if self.prev_mouse_pos != info.mouse_pos {
|
if self.prev_mouse_pos != info.mouse_pos {
|
||||||
self.prev_mouse_pos = info.mouse_pos;
|
self.prev_mouse_pos = info.mouse_pos;
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
|
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
||||||
|
|
||||||
use musicdb_lib::server::Command;
|
use musicdb_lib::server::Command;
|
||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
|
use speedy2d::{
|
||||||
|
color::Color,
|
||||||
|
dimen::Vec2,
|
||||||
|
shape::Rectangle,
|
||||||
|
window::{KeyScancode, ModifiersState, MouseButton, VirtualKeyCode},
|
||||||
|
Graphics2D,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
gui::{
|
||||||
|
DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren, KeyAction, KeyActionId,
|
||||||
|
KeyBinding,
|
||||||
|
},
|
||||||
gui_base::{Button, Panel, ScrollBox, Slider},
|
gui_base::{Button, Panel, ScrollBox, Slider},
|
||||||
gui_text::Label,
|
gui_text::{AdvancedContent, AdvancedLabel, Content, Label},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
@ -57,6 +68,10 @@ pub struct SettingsContent {
|
|||||||
pub scroll_sensitivity: Panel<(Label, Slider)>,
|
pub scroll_sensitivity: Panel<(Label, Slider)>,
|
||||||
pub idle_time: Panel<(Label, Slider)>,
|
pub idle_time: Panel<(Label, Slider)>,
|
||||||
pub save_button: Button<[Label; 1]>,
|
pub save_button: Button<[Label; 1]>,
|
||||||
|
pub keybinds: Vec<Panel<(AdvancedLabel, KeybindInput)>>,
|
||||||
|
pub keybinds_should_be_updated: Arc<AtomicBool>,
|
||||||
|
pub keybinds_updated: bool,
|
||||||
|
pub keybinds_updater: Arc<Mutex<Option<Vec<Panel<(AdvancedLabel, KeybindInput)>>>>>,
|
||||||
}
|
}
|
||||||
impl GuiElemChildren for SettingsContent {
|
impl GuiElemChildren for SettingsContent {
|
||||||
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
|
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
|
||||||
@ -70,11 +85,171 @@ impl GuiElemChildren for SettingsContent {
|
|||||||
self.idle_time.elem_mut(),
|
self.idle_time.elem_mut(),
|
||||||
self.save_button.elem_mut(),
|
self.save_button.elem_mut(),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter()
|
||||||
|
.chain(self.keybinds.iter_mut().map(|v| v.elem_mut())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
7
|
7 + self.keybinds.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct KeybindInput {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
c_label: Label,
|
||||||
|
id: KeyActionId,
|
||||||
|
changing: bool,
|
||||||
|
keybinds_should_be_updated: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
impl KeybindInput {
|
||||||
|
pub fn new(
|
||||||
|
config: GuiElemCfg,
|
||||||
|
id: KeyActionId,
|
||||||
|
_action: &KeyAction,
|
||||||
|
binding: Option<KeyBinding>,
|
||||||
|
keybinds_should_be_updated: Arc<AtomicBool>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
config: config.w_keyboard_focus().w_mouse(),
|
||||||
|
c_label: Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
if let Some(b) = &binding {
|
||||||
|
format!(
|
||||||
|
"{}{}{}{}{:?}",
|
||||||
|
if b.get_ctrl() { "Ctrl+" } else { "" },
|
||||||
|
if b.get_alt() { "Alt+" } else { "" },
|
||||||
|
if b.get_shift() { "Shift+" } else { "" },
|
||||||
|
if b.get_meta() { "Meta+" } else { "" },
|
||||||
|
b.key,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("")
|
||||||
|
},
|
||||||
|
Color::WHITE,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.5, 0.5),
|
||||||
|
),
|
||||||
|
id,
|
||||||
|
changing: false,
|
||||||
|
keybinds_should_be_updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl GuiElem for KeybindInput {
|
||||||
|
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||||
|
if let MouseButton::Left = button {
|
||||||
|
self.changing = true;
|
||||||
|
self.config.request_keyboard_focus = true;
|
||||||
|
vec![GuiAction::ResetKeyboardFocus]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn key_focus(
|
||||||
|
&mut self,
|
||||||
|
modifiers: ModifiersState,
|
||||||
|
down: bool,
|
||||||
|
key: Option<VirtualKeyCode>,
|
||||||
|
_scan: KeyScancode,
|
||||||
|
) -> Vec<GuiAction> {
|
||||||
|
if self.changing && down {
|
||||||
|
if let Some(key) = key {
|
||||||
|
if !matches!(
|
||||||
|
key,
|
||||||
|
VirtualKeyCode::LControl
|
||||||
|
| VirtualKeyCode::RControl
|
||||||
|
| VirtualKeyCode::LShift
|
||||||
|
| VirtualKeyCode::RShift
|
||||||
|
| VirtualKeyCode::LAlt
|
||||||
|
| VirtualKeyCode::RAlt
|
||||||
|
| VirtualKeyCode::LWin
|
||||||
|
| VirtualKeyCode::RWin
|
||||||
|
) {
|
||||||
|
self.changing = false;
|
||||||
|
let bind = KeyBinding::new(&modifiers, key);
|
||||||
|
self.keybinds_should_be_updated
|
||||||
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
vec![
|
||||||
|
GuiAction::SetKeybind(self.id, Some(bind)),
|
||||||
|
GuiAction::ResetKeyboardFocus,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
|
||||||
|
if info.has_keyboard_focus && self.changing {
|
||||||
|
let half_width = 2.0;
|
||||||
|
let thickness = 2.0 * half_width;
|
||||||
|
g.draw_line(
|
||||||
|
Vec2::new(info.pos.top_left().x, info.pos.top_left().y + half_width),
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.bottom_right().x,
|
||||||
|
info.pos.top_left().y + half_width,
|
||||||
|
),
|
||||||
|
thickness,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
g.draw_line(
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.top_left().x,
|
||||||
|
info.pos.bottom_right().y - half_width,
|
||||||
|
),
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.bottom_right().x,
|
||||||
|
info.pos.bottom_right().y - half_width,
|
||||||
|
),
|
||||||
|
thickness,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
g.draw_line(
|
||||||
|
Vec2::new(info.pos.top_left().x + half_width, info.pos.top_left().y),
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.top_left().x + half_width,
|
||||||
|
info.pos.bottom_right().y,
|
||||||
|
),
|
||||||
|
thickness,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
g.draw_line(
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.bottom_right().x - half_width,
|
||||||
|
info.pos.top_left().y,
|
||||||
|
),
|
||||||
|
Vec2::new(
|
||||||
|
info.pos.bottom_right().x - half_width,
|
||||||
|
info.pos.bottom_right().y,
|
||||||
|
),
|
||||||
|
thickness,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn config(&self) -> &GuiElemCfg {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
fn config_mut(&mut self) -> &mut GuiElemCfg {
|
||||||
|
&mut self.config
|
||||||
|
}
|
||||||
|
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
|
||||||
|
Box::new([self.c_label.elem_mut()].into_iter())
|
||||||
|
}
|
||||||
|
fn any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn elem(&self) -> &dyn GuiElem {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn elem_mut(&mut self) -> &mut dyn GuiElem {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SettingsContent {
|
impl SettingsContent {
|
||||||
@ -273,6 +448,41 @@ impl SettingsContent {
|
|||||||
Vec2::new(0.5, 0.5),
|
Vec2::new(0.5, 0.5),
|
||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
|
keybinds: vec![],
|
||||||
|
keybinds_should_be_updated: Arc::new(AtomicBool::new(true)),
|
||||||
|
keybinds_updated: false,
|
||||||
|
keybinds_updater: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn draw(&mut self, info: &mut DrawInfo) -> bool {
|
||||||
|
if !self.keybinds_updated
|
||||||
|
&& self
|
||||||
|
.keybinds_should_be_updated
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
self.keybinds_updated = true;
|
||||||
|
self.keybinds_should_be_updated
|
||||||
|
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let updater = Arc::clone(&self.keybinds_updater);
|
||||||
|
let keybinds_should_be_updated = Arc::clone(&self.keybinds_should_be_updated);
|
||||||
|
info.actions.push(GuiAction::Do(Box::new(move |gui| {
|
||||||
|
*updater.lock().unwrap() =
|
||||||
|
Some(build_keybind_elems(gui, &keybinds_should_be_updated))
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
if self.keybinds_updated {
|
||||||
|
if let Some(keybinds) = self.keybinds_updater.lock().unwrap().take() {
|
||||||
|
self.keybinds_updated = false;
|
||||||
|
self.keybinds = keybinds;
|
||||||
|
if let Some(h) = &info.helper {
|
||||||
|
h.request_redraw();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,6 +509,9 @@ impl GuiElem for Settings {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
|
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
|
||||||
|
if self.c_scroll_box.children.draw(info) {
|
||||||
|
self.c_scroll_box.config_mut().redraw = true;
|
||||||
|
}
|
||||||
let scrollbox = &mut self.c_scroll_box;
|
let scrollbox = &mut self.c_scroll_box;
|
||||||
let background = &mut self.c_background;
|
let background = &mut self.c_background;
|
||||||
let settings_opacity_slider = &mut scrollbox.children.opacity.children.1;
|
let settings_opacity_slider = &mut scrollbox.children.opacity.children.1;
|
||||||
@ -317,7 +530,7 @@ impl GuiElem for Settings {
|
|||||||
scrollbox.config_mut().redraw = true;
|
scrollbox.config_mut().redraw = true;
|
||||||
if scrollbox.children_heights.len() == scrollbox.children.len() {
|
if scrollbox.children_heights.len() == scrollbox.children.len() {
|
||||||
for (i, h) in scrollbox.children_heights.iter_mut().enumerate() {
|
for (i, h) in scrollbox.children_heights.iter_mut().enumerate() {
|
||||||
*h = if i == 0 {
|
*h = if i == 0 || i >= 7 {
|
||||||
info.line_height * 2.0
|
info.line_height * 2.0
|
||||||
} else {
|
} else {
|
||||||
info.line_height
|
info.line_height
|
||||||
@ -330,3 +543,65 @@ impl GuiElem for Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_keybind_elems(
|
||||||
|
gui: &crate::gui::Gui,
|
||||||
|
keybinds_should_be_updated: &Arc<AtomicBool>,
|
||||||
|
) -> Vec<Panel<(AdvancedLabel, KeybindInput)>> {
|
||||||
|
let split = 0.75;
|
||||||
|
let mut list = gui
|
||||||
|
.key_actions
|
||||||
|
.iter()
|
||||||
|
.map(|(a, b)| (a, b, None))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for (binding, action) in gui.keybinds.iter() {
|
||||||
|
list[action.get_index()].2 = Some(*binding);
|
||||||
|
}
|
||||||
|
list.sort_by_key(|(_, v, _)| &v.category);
|
||||||
|
list.into_iter()
|
||||||
|
.map(|(id, v, binding)| {
|
||||||
|
Panel::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
(
|
||||||
|
AdvancedLabel::new(
|
||||||
|
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (split, 1.0))),
|
||||||
|
Vec2::new(1.0, 0.5),
|
||||||
|
vec![
|
||||||
|
vec![(
|
||||||
|
AdvancedContent::Text(Content::new(
|
||||||
|
format!("{}", v.title),
|
||||||
|
if v.enabled {
|
||||||
|
Color::WHITE
|
||||||
|
} else {
|
||||||
|
Color::LIGHT_GRAY
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
)],
|
||||||
|
vec![(
|
||||||
|
AdvancedContent::Text(Content::new(
|
||||||
|
format!("{}", v.description),
|
||||||
|
if v.enabled {
|
||||||
|
Color::LIGHT_GRAY
|
||||||
|
} else {
|
||||||
|
Color::GRAY
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
0.5,
|
||||||
|
1.0,
|
||||||
|
)],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
KeybindInput::new(
|
||||||
|
GuiElemCfg::at(Rectangle::from_tuples((split, 0.0), (1.0, 1.0))),
|
||||||
|
id,
|
||||||
|
v,
|
||||||
|
binding,
|
||||||
|
Arc::clone(keybinds_should_be_updated),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user