diff --git a/src/lib.rs b/src/lib.rs index 06afdfe7..607edd30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,10 +61,10 @@ use model_loader::ModelLoader; use render::{DamageDigitMaterial, RoseRenderPlugin}; use resources::{ load_ui_resources, run_network_thread, ui_requested_cursor_apply_system, update_ui_resources, - AppState, ClientEntityList, DamageDigitsSpawner, DebugRenderConfig, GameData, InterfaceConfig, - NameTagCache, NetworkThread, NetworkThreadMessage, RenderConfiguration, SelectedTarget, - ServerConfiguration, SoundCache, SoundConfig, SpecularTexture, VfsResource, WorldTime, - ZoneTime, + AppState, ClientEntityList, DamageDigitsSpawner, DebugRenderConfig, GameData, HotkeysConfig, + InterfaceConfig, NameTagCache, NetworkThread, NetworkThreadMessage, RenderConfiguration, + SelectedTarget, ServerConfiguration, SoundCache, SoundConfig, SpecularTexture, VfsResource, + WorldTime, ZoneTime, }; use scripting::RoseScriptingPlugin; use systems::{ @@ -316,6 +316,7 @@ pub struct Config { pub server: ServerConfig, pub sound: SoundConfig, pub interface: InterfaceConfig, + pub hotkeys: HotkeysConfig, } pub fn load_config(path: &PathBuf) -> Config { diff --git a/src/resources/hotkeys_config.rs b/src/resources/hotkeys_config.rs new file mode 100644 index 00000000..034ad178 --- /dev/null +++ b/src/resources/hotkeys_config.rs @@ -0,0 +1,155 @@ +use egui::{Key, KeyboardShortcut, Modifiers}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Clone)] +#[serde(default)] +pub struct HotkeysConfig { + #[serde(with = "KeyboardShortcutDef")] + pub inventory: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub skills: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub character: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub quests: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub clan: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub settings: KeyboardShortcut, + + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_1: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_2: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_3: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_4: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_5: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_6: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_7: KeyboardShortcut, + #[serde(with = "KeyboardShortcutDef")] + pub hotbar_8: KeyboardShortcut, +} + +impl Default for HotkeysConfig { + fn default() -> Self { + Self { + inventory: KeyboardShortcut::new(Modifiers::ALT, Key::I), + skills: KeyboardShortcut::new(Modifiers::ALT, Key::S), + character: KeyboardShortcut::new(Modifiers::ALT, Key::A), + quests: KeyboardShortcut::new(Modifiers::ALT, Key::Q), + clan: KeyboardShortcut::new(Modifiers::ALT, Key::N), + settings: KeyboardShortcut::new(Modifiers::ALT, Key::O), + + hotbar_1: KeyboardShortcut::new(Modifiers::NONE, Key::F1), + hotbar_2: KeyboardShortcut::new(Modifiers::NONE, Key::F2), + hotbar_3: KeyboardShortcut::new(Modifiers::NONE, Key::F3), + hotbar_4: KeyboardShortcut::new(Modifiers::NONE, Key::F4), + hotbar_5: KeyboardShortcut::new(Modifiers::NONE, Key::F5), + hotbar_6: KeyboardShortcut::new(Modifiers::NONE, Key::F6), + hotbar_7: KeyboardShortcut::new(Modifiers::NONE, Key::F7), + hotbar_8: KeyboardShortcut::new(Modifiers::NONE, Key::F8), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "KeyboardShortcut")] +struct KeyboardShortcutDef { + #[serde(with = "ModifiersDef")] + pub modifiers: Modifiers, + #[serde(with = "KeyDef")] + pub key: Key, +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "Modifiers")] +struct ModifiersDef { + pub alt: bool, + pub ctrl: bool, + pub shift: bool, + pub mac_cmd: bool, + pub command: bool, +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "Key")] +enum KeyDef { + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + Escape, + Tab, + Backspace, + Enter, + Space, + Insert, + Delete, + Home, + End, + PageUp, + PageDown, + Minus, + PlusEquals, + Num0, + Num1, + Num2, + Num3, + Num4, + Num5, + Num6, + Num7, + Num8, + Num9, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs index f8f37712..dbf90124 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -9,6 +9,7 @@ mod debug_inspector; mod debug_render; mod game_connection; mod game_data; +mod hotkeys_config; mod interface_config; mod login_connection; mod login_state; @@ -40,6 +41,7 @@ pub use debug_inspector::DebugInspector; pub use debug_render::DebugRenderConfig; pub use game_connection::GameConnection; pub use game_data::GameData; +pub use hotkeys_config::HotkeysConfig; pub use interface_config::{InterfaceConfig, TargetingType}; pub use login_connection::LoginConnection; pub use login_state::LoginState; diff --git a/src/ui/ui_game_menu_system.rs b/src/ui/ui_game_menu_system.rs index cc3fe613..2cd13d3a 100644 --- a/src/ui/ui_game_menu_system.rs +++ b/src/ui/ui_game_menu_system.rs @@ -1,4 +1,4 @@ -use bevy::prelude::{Assets, EventWriter, Local, Res, ResMut}; +use bevy::prelude::{Assets, EventWriter, Res, ResMut}; use bevy_egui::{egui, EguiContexts}; use crate::{ @@ -7,6 +7,7 @@ use crate::{ widgets::{DataBindings, Dialog}, UiSoundEvent, UiStateWindows, }, + Config, }; const IID_BTN_CHAR: i32 = 10; @@ -20,19 +21,13 @@ const IID_BTN_INFO: i32 = 17; const IID_BTN_OPTION: i32 = 18; const IID_BTN_EXIT: i32 = 19; -#[derive(Default)] -pub struct UiGameMenuState { - pub was_open: bool, - pub mouse_up_after_open: bool, -} - pub fn ui_game_menu_system( mut egui_context: EguiContexts, mut ui_state_windows: ResMut, - mut ui_state: Local, ui_resources: Res, mut ui_sound_events: EventWriter, dialog_assets: Res>, + config: Res, ) { let dialog = if let Some(dialog) = dialog_assets.get(&ui_resources.dialog_game_menu) { dialog @@ -51,7 +46,7 @@ pub fn ui_game_menu_system( let mut response_button_help = None; let mut response_button_info = None; - let response = egui::Window::new("Game Menu") + egui::Window::new("Game Menu") .frame(egui::Frame::none()) .open(&mut ui_state_windows.menu_open) .title_bar(false) @@ -82,100 +77,69 @@ pub fn ui_game_menu_system( ); }); - if let Some(response) = response { - // To avoid clicked_elsewhere being triggered as soon as we open menu, - // we will only look for it after we have detected all mouse buttons - // have been released after opening - if ui_state.mouse_up_after_open { - if response.response.clicked_elsewhere() { - ui_state_windows.menu_open = false; - } - } else if !response - .response - .ctx - .input(|input| input.pointer.any_down()) - { - ui_state.mouse_up_after_open = true; - } - } else { - ui_state.mouse_up_after_open = false; - } - if response_button_character_info.map_or(false, |r| r.clicked()) { ui_state_windows.character_info_open = !ui_state_windows.character_info_open; - ui_state_windows.menu_open = false; } if response_button_inventory.map_or(false, |r| r.clicked()) { ui_state_windows.inventory_open = !ui_state_windows.inventory_open; - ui_state_windows.menu_open = false; } if response_button_skill_list.map_or(false, |r| r.clicked()) { ui_state_windows.skill_list_open = !ui_state_windows.skill_list_open; - ui_state_windows.menu_open = false; } if response_button_quest_list.map_or(false, |r| r.clicked()) { ui_state_windows.quest_list_open = !ui_state_windows.quest_list_open; - ui_state_windows.menu_open = false; } if response_button_options.map_or(false, |r| r.clicked()) { ui_state_windows.settings_open = !ui_state_windows.settings_open; - ui_state_windows.menu_open = false; } if response_button_community.map_or(false, |r| r.clicked()) { // TODO: Community dialog - ui_state_windows.menu_open = false; } if response_button_clan.map_or(false, |r| r.clicked()) { ui_state_windows.clan_open = !ui_state_windows.clan_open; - ui_state_windows.menu_open = false; } if response_button_help.map_or(false, |r| r.clicked()) { // TODO: Help dialog - ui_state_windows.menu_open = false; } if response_button_info.map_or(false, |r| r.clicked()) { // TODO: Info dialog - ui_state_windows.menu_open = false; } if response_button_exit.map_or(false, |r| r.clicked()) { // TODO: Exit dialog - ui_state_windows.menu_open = false; } if !egui_context.ctx_mut().wants_keyboard_input() { egui_context.ctx_mut().input_mut(|input| { - if input.consume_key(egui::Modifiers::ALT, egui::Key::A) { + if input.consume_shortcut(&config.hotkeys.character) { ui_state_windows.character_info_open = !ui_state_windows.character_info_open; } - if input.consume_key(egui::Modifiers::ALT, egui::Key::I) - || input.consume_key(egui::Modifiers::ALT, egui::Key::V) - { + if input.consume_shortcut(&config.hotkeys.inventory) { ui_state_windows.inventory_open = !ui_state_windows.inventory_open; } - if input.consume_key(egui::Modifiers::ALT, egui::Key::N) { + if input.consume_shortcut(&config.hotkeys.clan) { ui_state_windows.clan_open = !ui_state_windows.clan_open; } - if input.consume_key(egui::Modifiers::ALT, egui::Key::S) { + if input.consume_shortcut(&config.hotkeys.skills) { ui_state_windows.skill_list_open = !ui_state_windows.skill_list_open; } - if input.consume_key(egui::Modifiers::ALT, egui::Key::Q) { + if input.consume_shortcut(&config.hotkeys.quests) { ui_state_windows.quest_list_open = !ui_state_windows.quest_list_open; } - if input.consume_key(egui::Modifiers::ALT, egui::Key::O) { + if input.consume_shortcut(&config.hotkeys.settings) { ui_state_windows.settings_open = !ui_state_windows.settings_open; } }); diff --git a/src/ui/ui_hotbar_system.rs b/src/ui/ui_hotbar_system.rs index e506b8bd..a9863b9c 100644 --- a/src/ui/ui_hotbar_system.rs +++ b/src/ui/ui_hotbar_system.rs @@ -1,7 +1,6 @@ use bevy::{ ecs::query::WorldQuery, - input::Input, - prelude::{Assets, EventWriter, KeyCode, Local, Query, Res, ResMut, With}, + prelude::{Assets, EventWriter, Local, Query, Res, ResMut, With}, }; use bevy_egui::{egui, EguiContexts}; @@ -20,6 +19,7 @@ use crate::{ widgets::{DataBindings, Dialog, Widget}, DialogInstance, DragAndDropId, DragAndDropSlot, UiSoundEvent, UiStateDragAndDrop, }, + Config, }; const IID_BG_VERTICAL: i32 = 5; @@ -204,10 +204,10 @@ pub fn ui_hotbar_system( mut query_player: Query>, query_player_tooltip: Query>, mut player_command_events: EventWriter, - keyboard_input: Res>, game_data: Res, ui_resources: Res, dialog_assets: Res>, + config: Res, ) { let ui_state_hot_bar = &mut *ui_state_hot_bar; let dialog = if let Some(dialog) = ui_state_hot_bar @@ -227,25 +227,27 @@ pub fn ui_hotbar_system( let player_tooltip_data = query_player_tooltip.get_single().ok(); let use_hotbar_index = if !egui_context.ctx_mut().wants_keyboard_input() { - if keyboard_input.just_pressed(KeyCode::F1) { - Some(0) - } else if keyboard_input.just_pressed(KeyCode::F2) { - Some(1) - } else if keyboard_input.just_pressed(KeyCode::F3) { - Some(2) - } else if keyboard_input.just_pressed(KeyCode::F4) { - Some(3) - } else if keyboard_input.just_pressed(KeyCode::F5) { - Some(4) - } else if keyboard_input.just_pressed(KeyCode::F6) { - Some(5) - } else if keyboard_input.just_pressed(KeyCode::F7) { - Some(6) - } else if keyboard_input.just_pressed(KeyCode::F8) { - Some(7) - } else { - None - } + egui_context.ctx_mut().input_mut(|input| { + if input.consume_shortcut(&config.hotkeys.hotbar_1) { + Some(0) + } else if input.consume_shortcut(&config.hotkeys.hotbar_2) { + Some(1) + } else if input.consume_shortcut(&config.hotkeys.hotbar_3) { + Some(2) + } else if input.consume_shortcut(&config.hotkeys.hotbar_4) { + Some(3) + } else if input.consume_shortcut(&config.hotkeys.hotbar_5) { + Some(4) + } else if input.consume_shortcut(&config.hotkeys.hotbar_6) { + Some(5) + } else if input.consume_shortcut(&config.hotkeys.hotbar_7) { + Some(6) + } else if input.consume_shortcut(&config.hotkeys.hotbar_8) { + Some(7) + } else { + None + } + }) } else { None }; diff --git a/src/ui/ui_player_info_system.rs b/src/ui/ui_player_info_system.rs index ea20aa9a..1b88c958 100644 --- a/src/ui/ui_player_info_system.rs +++ b/src/ui/ui_player_info_system.rs @@ -211,7 +211,13 @@ pub fn ui_player_info_system( } } - if response_menu_button.map_or(false, |r| r.clicked()) { - ui_state_windows.menu_open = !ui_state_windows.menu_open; + if let Some(response_menu_button) = response_menu_button { + if response_menu_button.clicked() { + ui_state_windows.menu_open = !ui_state_windows.menu_open; + } + + if response_menu_button.clicked_elsewhere() { + ui_state_windows.menu_open = false; + } } } diff --git a/src/ui/ui_settings_system.rs b/src/ui/ui_settings_system.rs index 7d55943b..2260e2d5 100644 --- a/src/ui/ui_settings_system.rs +++ b/src/ui/ui_settings_system.rs @@ -1,7 +1,3 @@ -use bevy::prelude::{Local, Query, ResMut}; -use bevy_egui::{egui, EguiContexts}; -use std::path::Path; - use crate::{ audio::SoundGain, components::{NameTagType, SoundCategory}, @@ -10,11 +6,16 @@ use crate::{ ui::UiStateWindows, Config, }; +use bevy::prelude::{Local, Query, ResMut}; +use bevy_egui::{egui, EguiContexts}; +use egui::{vec2, KeyboardShortcut, Ui}; +use std::path::Path; #[derive(Copy, Clone, PartialEq, Debug)] enum SettingsPage { Sound, Interface, + Hotkeys, } pub struct UiStateSettings { @@ -40,6 +41,7 @@ pub fn ui_settings_system( egui::Window::new("Settings") .open(&mut ui_state_windows.settings_open) .resizable(false) + .fixed_size(vec2(200.0, 200.0)) .show(egui_context.ctx_mut(), |ui| { let mut save_settings = false; @@ -50,6 +52,12 @@ pub fn ui_settings_system( SettingsPage::Interface, "Interface", ); + + ui.selectable_value( + &mut ui_state_settings.page, + SettingsPage::Hotkeys, + "Key Bindings", + ); }); match ui_state_settings.page { @@ -165,6 +173,65 @@ pub fn ui_settings_system( } }); } + SettingsPage::Hotkeys => { + egui::Grid::new("hotkey_settings") + .num_columns(2) + .show(ui, |ui| { + let mut add_shortcut_setting = + |ui: &mut Ui, text: &str, value: &mut KeyboardShortcut| { + ui.label(text); + + let mut shortcut_label = + ui.ctx().format_shortcut(&value.clone()); + let response = + ui.add(egui::TextEdit::singleline(&mut shortcut_label)); + + if response.has_focus() { + let shortcut_option = ui.input_mut(|state| { + if state.keys_down.is_empty() { + return None; + } + + let key = state.keys_down.iter().next()?; + let shortcut = + KeyboardShortcut::new(state.modifiers, *key); + state.consume_shortcut(&shortcut); + + Some(shortcut) + }); + + if let Some(new_shortcut) = shortcut_option { + response.surrender_focus(); + *value = new_shortcut; + } + } + + if response.lost_focus() { + save_settings = true; + } + + ui.end_row(); + }; + + add_shortcut_setting(ui, "Inventory", &mut config.hotkeys.inventory); + add_shortcut_setting(ui, "Skills", &mut config.hotkeys.skills); + add_shortcut_setting(ui, "Character", &mut config.hotkeys.character); + add_shortcut_setting(ui, "Quest Log", &mut config.hotkeys.quests); + add_shortcut_setting(ui, "Clan", &mut config.hotkeys.clan); + add_shortcut_setting(ui, "Settings", &mut config.hotkeys.settings); + + ui.end_row(); + + add_shortcut_setting(ui, "Hotbar Slot 1", &mut config.hotkeys.hotbar_1); + add_shortcut_setting(ui, "Hotbar Slot 2", &mut config.hotkeys.hotbar_2); + add_shortcut_setting(ui, "Hotbar Slot 3", &mut config.hotkeys.hotbar_3); + add_shortcut_setting(ui, "Hotbar Slot 4", &mut config.hotkeys.hotbar_4); + add_shortcut_setting(ui, "Hotbar Slot 5", &mut config.hotkeys.hotbar_5); + add_shortcut_setting(ui, "Hotbar Slot 6", &mut config.hotkeys.hotbar_6); + add_shortcut_setting(ui, "Hotbar Slot 7", &mut config.hotkeys.hotbar_7); + add_shortcut_setting(ui, "Hotbar Slot 8", &mut config.hotkeys.hotbar_8); + }); + } }; if !save_settings {