diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index 2141327..ce30ca1 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -1,6 +1,6 @@ use std::{ any::Any, - collections::HashMap, + collections::{BTreeMap, HashMap}, io::Cursor, net::TcpStream, sync::{mpsc::Sender, Arc, Mutex}, @@ -290,6 +290,8 @@ pub struct Gui { pub size: UVec2, pub mouse_pos: Vec2, pub font: Font, + pub keybinds: BTreeMap, + pub key_actions: KeyActions, pub covers: Option>, pub custom_images: Option>, pub modifiers: ModifiersState, @@ -448,6 +450,8 @@ impl Gui { size: UVec2::ZERO, mouse_pos: Vec2::ZERO, font, + keybinds: BTreeMap::new(), + key_actions: KeyActions::default(), covers: Some(HashMap::new()), custom_images: Some(HashMap::new()), // font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(), @@ -465,6 +469,32 @@ impl Gui { 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. @@ -547,8 +577,14 @@ pub(crate) trait GuiElemInternal: GuiElem { 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 self.draw(info, g); + // reset cfg + self.config_mut().init = false; // reset info info.has_keyboard_focus = false; let focus_path = info.child_has_keyboard_focus; @@ -626,9 +662,18 @@ pub(crate) trait GuiElemInternal: GuiElem { &mut |v: &mut dyn GuiElem| { if v.config().mouse_events { match button { - MouseButton::Left => v.config_mut().mouse_down.0 = true, - MouseButton::Middle => v.config_mut().mouse_down.1 = true, - MouseButton::Right => v.config_mut().mouse_down.2 = true, + MouseButton::Left => { + v.config_mut().mouse_down.0 = 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(_) => {} } Some(v.mouse_down(button)) @@ -660,9 +705,18 @@ pub(crate) trait GuiElemInternal: GuiElem { self._recursive_all(&mut |v| { if v.config().mouse_events { match button { - MouseButton::Left => v.config_mut().mouse_down.0 = false, - MouseButton::Middle => v.config_mut().mouse_down.1 = false, - MouseButton::Right => v.config_mut().mouse_down.2 = false, + MouseButton::Left => { + v.config_mut().mouse_down.0 = 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(_) => {} } vec.extend(v.mouse_up(button)); @@ -947,6 +1001,9 @@ pub struct GuiElemCfg { /// if true, indicates that something (text size, screen size, ...) has changed /// and you should probably relayout and redraw from scratch. 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. /// ((0, 0), (1, 1)) is the default and fills all available space. /// ((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(). /// this can act like a "previous pos" field within draw. 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), + /// 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 pub mouse_events: bool, /// 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 { enabled: true, redraw: false, + init: true, pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)), pixel_pos: Rectangle::ZERO, mouse_down: (false, false, false), + mouse_pressed: (false, false, false), mouse_events: false, scroll_events: false, keyboard_events_watch: false, @@ -1031,6 +1092,11 @@ impl Default for GuiElemCfg { #[allow(unused)] pub enum GuiAction { OpenMain, + /// Add a key action (and optionally bind it) and then call the given function + AddKeybind(Option, KeyAction, Box), + /// Binds the action to the keybinding, or unbinds it entirely + SetKeybind(KeyActionId, Option), + ForceIdle, /// false -> prevent idling, true -> end idling even if already idle EndIdle(bool), SetHighPerformance(bool), @@ -1042,6 +1108,7 @@ pub enum GuiAction { ContextMenu(Option<(Vec>)>), /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one. ResetKeyboardFocus, + SetFocused(SpecificGuiElem), SetDragging( Option<( Dragging, @@ -1064,6 +1131,11 @@ pub enum Dragging { Queue(Queue), Queues(Vec), } +pub enum SpecificGuiElem { + SearchArtist, + SearchAlbum, + SearchSong, +} /// GuiElems have access to this within draw. /// 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. /// compare this to `pos` to find the mouse's relative position. pub mouse_pos: Vec2, + /// true if `info.pos.contains(info.mouse_pos)`. + pub mouse_pos_in_bounds: bool, pub helper: Option<&'a mut WindowHelper>, pub get_con: Arc>>, pub covers: &'a mut HashMap, @@ -1114,6 +1188,34 @@ impl Gui { 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::>() + { + 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) => { #[cfg(debug_assertions)] eprintln!("[DEBUG] Sending command to server: {cmd:?}"); @@ -1122,6 +1224,11 @@ impl Gui { } } 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::SetDragging(d) => self.dragging = d, GuiAction::SetHighPerformance(d) => self.high_performance = d, @@ -1172,6 +1279,9 @@ impl Gui { } GuiAction::Do(f) => f(self), GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit), + GuiAction::ForceIdle => { + self.gui.force_idle(); + } GuiAction::EndIdle(v) => { if v { self.gui.unidle(); @@ -1219,6 +1329,7 @@ impl WindowHandler for Gui { database: &mut *dblock, font: &self.font, mouse_pos: self.mouse_pos, + mouse_pos_in_bounds: false, get_con: Arc::clone(&self.get_con), covers: &mut covers, custom_images: &mut custom_images, @@ -1388,6 +1499,17 @@ impl WindowHandler for Gui { scancode: KeyScancode, ) { 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 !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) { 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 Vec>, + pub enabled: bool, +} +impl KeyAction { + pub fn execute(&mut self) -> Vec { + if self.enabled { + (self.action)() + } else { + vec![] + } + } +} +#[derive(Default)] +pub struct KeyActions(Vec); +#[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 { + self.0.iter().enumerate().map(|(i, v)| (KeyActionId(i), v)) + } +} + pub fn morph_rect(a: &Rectangle, b: &Rectangle, p: f32) -> Rectangle { let q = 1.0 - p; Rectangle::from_tuples( diff --git a/musicdb-client/src/gui_base.rs b/musicdb-client/src/gui_base.rs index a1bf156..e4de396 100755 --- a/musicdb-client/src/gui_base.rs +++ b/musicdb-client/src/gui_base.rs @@ -611,7 +611,7 @@ impl GuiElem for Slider { ); let line_left = line_pos.top_left().x; let line_width = line_pos.width(); - if self.config.mouse_down.0 { + if self.config.mouse_pressed.0 { self.val = self.min + (self.max - self.min) * 1.0f64.min(0.0f64.max( diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index ec13d59..7498289 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -46,13 +46,13 @@ with Regex search and drag-n-drop. pub struct LibraryBrowser { config: GuiElemCfg, - c_search_artist: TextField, - c_search_album: TextField, - c_search_song: TextField, - c_scroll_box: ScrollBox>, - c_filter_button: Button<[Label; 1]>, - c_filter_panel: FilterPanel, - c_selected_counter_panel: Panel<[Label; 1]>, + pub c_search_artist: TextField, + pub c_search_album: TextField, + pub c_search_song: TextField, + pub c_scroll_box: ScrollBox>, + pub c_filter_button: Button<[Label; 1]>, + pub c_filter_panel: FilterPanel, + pub c_selected_counter_panel: Panel<[Label; 1]>, // - - - library_sorted: Vec<(ArtistId, Vec, Vec<(AlbumId, Vec)>)>, library_filtered: Vec<( diff --git a/musicdb-client/src/gui_screen.rs b/musicdb-client/src/gui_screen.rs index 07dbbe3..789f00d 100755 --- a/musicdb-client/src/gui_screen.rs +++ b/musicdb-client/src/gui_screen.rs @@ -4,7 +4,10 @@ use musicdb_lib::{data::queue::QueueContent, server::Command}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D}; use crate::{ - gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren}, + gui::{ + DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren, KeyAction, KeyBinding, + SpecificGuiElem, + }, gui_anim::AnimationController, gui_base::{Button, Panel}, gui_edit_song::EditorForSongs, @@ -271,7 +274,10 @@ impl GuiElem for GuiScreen { key: Option, _scan: speedy2d::window::KeyScancode, ) -> Vec { - self.not_idle(); + if down { + // on releasing Ctrl in Ctrl+I => Idle keybind, don't unidle + self.not_idle(); + } if self.hotkey.triggered(modifiers, down, key) { self.config.request_keyboard_focus = true; vec![GuiAction::ResetKeyboardFocus] @@ -284,6 +290,45 @@ impl GuiElem for GuiScreen { vec![] } 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 if self.prev_mouse_pos != info.mouse_pos { self.prev_mouse_pos = info.mouse_pos; diff --git a/musicdb-client/src/gui_settings.rs b/musicdb-client/src/gui_settings.rs index 0d4c617..b2e0e5f 100755 --- a/musicdb-client/src/gui_settings.rs +++ b/musicdb-client/src/gui_settings.rs @@ -1,10 +1,21 @@ +use std::sync::{atomic::AtomicBool, Arc, Mutex}; + 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::{ - gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren}, + gui::{ + DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren, KeyAction, KeyActionId, + KeyBinding, + }, gui_base::{Button, Panel, ScrollBox, Slider}, - gui_text::Label, + gui_text::{AdvancedContent, AdvancedLabel, Content, Label}, }; pub struct Settings { @@ -57,6 +68,10 @@ pub struct SettingsContent { pub scroll_sensitivity: Panel<(Label, Slider)>, pub idle_time: Panel<(Label, Slider)>, pub save_button: Button<[Label; 1]>, + pub keybinds: Vec>, + pub keybinds_should_be_updated: Arc, + pub keybinds_updated: bool, + pub keybinds_updater: Arc>>>>, } impl GuiElemChildren for SettingsContent { fn iter(&mut self) -> Box + '_> { @@ -70,11 +85,171 @@ impl GuiElemChildren for SettingsContent { self.idle_time.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 { - 7 + 7 + self.keybinds.len() + } +} +pub struct KeybindInput { + config: GuiElemCfg, + c_label: Label, + id: KeyActionId, + changing: bool, + keybinds_should_be_updated: Arc, +} +impl KeybindInput { + pub fn new( + config: GuiElemCfg, + id: KeyActionId, + _action: &KeyAction, + binding: Option, + keybinds_should_be_updated: Arc, + ) -> 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 { + 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, + _scan: KeyScancode, + ) -> Vec { + 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 + '_> { + 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 { @@ -273,6 +448,41 @@ impl SettingsContent { 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 } 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 background = &mut self.c_background; let settings_opacity_slider = &mut scrollbox.children.opacity.children.1; @@ -317,7 +530,7 @@ impl GuiElem for Settings { scrollbox.config_mut().redraw = true; if scrollbox.children_heights.len() == scrollbox.children.len() { 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 } else { 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, +) -> Vec> { + let split = 0.75; + let mut list = gui + .key_actions + .iter() + .map(|(a, b)| (a, b, None)) + .collect::>(); + 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() +}