Display keys based off of scancodes

Queries the OS to translate physical keyboard scancodes into
Strings that can be shown in the UI.
Addresses issues #861 and #354
This commit is contained in:
Adam Blanchet 2021-03-31 14:56:56 +02:00
parent 522e89d57f
commit 0d7d069d41
No known key found for this signature in database
GPG Key ID: F37520485FDB97C9
11 changed files with 160 additions and 79 deletions

View File

@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bag tooltips only show slots now - Bag tooltips only show slots now
- Removed infinite armour values from most admin items - Removed infinite armour values from most admin items
- Item tooltips during trades will now inform the user of what ctrl-click and shift-click do - Item tooltips during trades will now inform the user of what ctrl-click and shift-click do
- International keyboards can now display more key names on Linux and Windows instead of `Unknown`.
### Removed ### Removed

47
Cargo.lock generated
View File

@ -2570,6 +2570,19 @@ dependencies = [
"winapi-build", "winapi-build",
] ]
[[package]]
name = "keyboard-keynames"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6dbe132877d7a0327c4282840703c2cb456312fae930898b5dc126844889626"
dependencies = [
"wayland-client 0.28.5",
"winapi 0.3.9",
"winit",
"xcb 0.8.2",
"xkbcommon",
]
[[package]] [[package]]
name = "khronos_api" name = "khronos_api"
version = "3.1.0" version = "3.1.0"
@ -2820,6 +2833,16 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.1.0" version = "0.1.0"
@ -5687,6 +5710,7 @@ dependencies = [
"image", "image",
"inline_tweak", "inline_tweak",
"itertools 0.10.0", "itertools 0.10.0",
"keyboard-keynames",
"lazy_static", "lazy_static",
"native-dialog", "native-dialog",
"num 0.4.0", "num 0.4.0",
@ -6435,7 +6459,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5e937afd03b64b7be4f959cc044e09260a47241b71e56933f37db097bf7859d" checksum = "e5e937afd03b64b7be4f959cc044e09260a47241b71e56933f37db097bf7859d"
dependencies = [ dependencies = [
"xcb", "xcb 0.9.0",
] ]
[[package]] [[package]]
@ -6471,6 +6495,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "xcb"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
dependencies = [
"libc",
"log",
]
[[package]] [[package]]
name = "xcb" name = "xcb"
version = "0.9.0" version = "0.9.0"
@ -6513,6 +6547,17 @@ dependencies = [
"xkbcommon-sys", "xkbcommon-sys",
] ]
[[package]]
name = "xkbcommon"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda0ea5f7ddabd51deeeda7799bee06274112f577da7dd3d954b8eda731b2fce"
dependencies = [
"libc",
"memmap",
"xcb 0.8.2",
]
[[package]] [[package]]
name = "xkbcommon-sys" name = "xkbcommon-sys"
version = "0.7.4" version = "0.7.4"

View File

@ -45,6 +45,7 @@ iced = {package = "iced_native", git = "https://github.com/hecrj/iced", rev = "8
iced_winit = {git = "https://github.com/hecrj/iced", rev = "8d882d787e6b7fd7c2435f42f82933e2ed904edf"} iced_winit = {git = "https://github.com/hecrj/iced", rev = "8d882d787e6b7fd7c2435f42f82933e2ed904edf"}
window_clipboard = "0.2" window_clipboard = "0.2"
glyph_brush = "0.7.0" glyph_brush = "0.7.0"
keyboard-keynames = "0.1.0"
# ECS # ECS
specs = {git = "https://github.com/amethyst/specs.git", rev = "5a9b71035007be0e3574f35184acac1cd4530496"} specs = {git = "https://github.com/amethyst/specs.git", rev = "5a9b71035007be0e3574f35184acac1cd4530496"}

View File

@ -126,6 +126,7 @@ impl<'a> Widget for Buttons<'a> {
Some(inv) => inv, Some(inv) => inv,
None => return None, None => return None,
}; };
let key_layout = &self.global_state.window.key_layout;
let localized_strings = self.localized_strings; let localized_strings = self.localized_strings;
let arrow_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer let arrow_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
@ -184,13 +185,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Bag) .get_binding(GameInput::Bag)
{ {
Text::new(bag.to_string().as_str()) Text::new(bag.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.bag_text_bg, ui); .set(state.ids.bag_text_bg, ui);
Text::new(bag.to_string().as_str()) Text::new(bag.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.bag_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.bag_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -245,13 +246,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Settings) .get_binding(GameInput::Settings)
{ {
Text::new(settings.to_string().as_str()) Text::new(settings.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.settings_button, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.settings_button, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.settings_text_bg, ui); .set(state.ids.settings_text_bg, ui);
Text::new(settings.to_string().as_str()) Text::new(settings.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.settings_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.settings_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -283,13 +284,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Social) .get_binding(GameInput::Social)
{ {
Text::new(social.to_string().as_str()) Text::new(social.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.social_button, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.social_button, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.social_text_bg, ui); .set(state.ids.social_text_bg, ui);
Text::new(social.to_string().as_str()) Text::new(social.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.social_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.social_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -320,13 +321,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Map) .get_binding(GameInput::Map)
{ {
Text::new(map.to_string().as_str()) Text::new(map.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.map_button, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.map_button, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.map_text_bg, ui); .set(state.ids.map_text_bg, ui);
Text::new(map.to_string().as_str()) Text::new(map.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.map_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.map_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -362,13 +363,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Spellbook) .get_binding(GameInput::Spellbook)
{ {
Text::new(spell.to_string().as_str()) Text::new(spell.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.spellbook_button, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.spellbook_button, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.spellbook_text_bg, ui); .set(state.ids.spellbook_text_bg, ui);
Text::new(spell.to_string().as_str()) Text::new(spell.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.spellbook_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.spellbook_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -422,13 +423,13 @@ impl<'a> Widget for Buttons<'a> {
.controls .controls
.get_binding(GameInput::Crafting) .get_binding(GameInput::Crafting)
{ {
Text::new(crafting.to_string().as_str()) Text::new(crafting.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.crafting_button, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.crafting_button, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.crafting_text_bg, ui); .set(state.ids.crafting_text_bg, ui);
Text::new(crafting.to_string().as_str()) Text::new(crafting.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.crafting_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.crafting_text_bg, 1.0, 1.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)

View File

@ -151,6 +151,7 @@ impl<'a> Widget for Group<'a> {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let mut events = Vec::new(); let mut events = Vec::new();
let localized_strings = self.localized_strings; let localized_strings = self.localized_strings;
let key_layout = &self.global_state.window.key_layout;
let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer
let debug_on = self.global_state.settings.interface.toggle_debug; let debug_on = self.global_state.settings.interface.toggle_debug;
let offset = if debug_on { 270.0 } else { 0.0 }; let offset = if debug_on { 270.0 } else { 0.0 };
@ -802,7 +803,7 @@ impl<'a> Widget for Group<'a> {
.settings .settings
.controls .controls
.get_binding(GameInput::AcceptGroupInvite) .get_binding(GameInput::AcceptGroupInvite)
.map_or_else(|| "".into(), |key| key.to_string()); .map_or_else(|| "".into(), |key| key.display_string(key_layout));
if Button::image(self.imgs.button) if Button::image(self.imgs.button)
.w_h(90.0, 22.0) .w_h(90.0, 22.0)
.bottom_left_with_margins_on(state.ids.bg, 15.0, 15.0) .bottom_left_with_margins_on(state.ids.bg, 15.0, 15.0)
@ -827,7 +828,7 @@ impl<'a> Widget for Group<'a> {
.settings .settings
.controls .controls
.get_binding(GameInput::DeclineGroupInvite) .get_binding(GameInput::DeclineGroupInvite)
.map_or_else(|| "".into(), |key| key.to_string()); .map_or_else(|| "".into(), |key| key.display_string(key_layout));
if Button::image(self.imgs.button) if Button::image(self.imgs.button)
.w_h(90.0, 22.0) .w_h(90.0, 22.0)
.bottom_right_with_margins_on(state.ids.bg, 15.0, 15.0) .bottom_right_with_margins_on(state.ids.bg, 15.0, 15.0)

View File

@ -953,6 +953,7 @@ impl Hud {
let fps = global_state.clock.stats().average_tps; let fps = global_state.clock.stats().average_tps;
let version = common::util::DISPLAY_VERSION_LONG.clone(); let version = common::util::DISPLAY_VERSION_LONG.clone();
let i18n = &*global_state.i18n.read(); let i18n = &*global_state.i18n.read();
let key_layout = &global_state.window.key_layout;
if self.show.ingame { if self.show.ingame {
let ecs = client.state().ecs(); let ecs = client.state().ecs();
@ -1389,6 +1390,7 @@ impl Hud {
&global_state.settings.controls, &global_state.settings.controls,
// If we're currently set to interact with the item... // If we're currently set to interact with the item...
active, active,
&global_state.window.key_layout,
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(pos) .position_ingame(pos)
@ -1434,6 +1436,7 @@ impl Hud {
&self.fonts, &self.fonts,
&global_state.settings.controls, &global_state.settings.controls,
true, true,
&global_state.window.key_layout,
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(over_pos) .position_ingame(over_pos)
@ -1791,21 +1794,19 @@ impl Hud {
.mid_top_with_margin_on(self.ids.intro_button, -20.0 + arrow_ani as f64) .mid_top_with_margin_on(self.ids.intro_button, -20.0 + arrow_ani as f64)
.color(Some(QUALITY_LEGENDARY)) .color(Some(QUALITY_LEGENDARY))
.set(self.ids.tut_arrow, ui_widgets); .set(self.ids.tut_arrow, ui_widgets);
Text::new( Text::new(&i18n.get("hud.tutorial_click_here").replace(
&i18n "{key}",
.get("hud.tutorial_click_here") toggle_cursor_key.display_string(key_layout).as_str(),
.replace("{key}", toggle_cursor_key.to_string().as_str()), ))
)
.mid_top_with_margin_on(self.ids.tut_arrow, -18.0) .mid_top_with_margin_on(self.ids.tut_arrow, -18.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.color(BLACK) .color(BLACK)
.set(self.ids.tut_arrow_txt_bg, ui_widgets); .set(self.ids.tut_arrow_txt_bg, ui_widgets);
Text::new( Text::new(&i18n.get("hud.tutorial_click_here").replace(
&i18n "{key}",
.get("hud.tutorial_click_here") toggle_cursor_key.display_string(key_layout).as_str(),
.replace("{key}", toggle_cursor_key.to_string().as_str()), ))
)
.bottom_right_with_margins_on(self.ids.tut_arrow_txt_bg, 1.0, 1.0) .bottom_right_with_margins_on(self.ids.tut_arrow_txt_bg, 1.0, 1.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
@ -2071,7 +2072,7 @@ impl Hud {
Text::new( Text::new(
&i18n &i18n
.get("hud.press_key_to_toggle_keybindings_fmt") .get("hud.press_key_to_toggle_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()), .replace("{key}", help_key.display_string(key_layout).as_str()),
) )
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.num_particles, 5.0) .down_from(self.ids.num_particles, 5.0)
@ -2085,11 +2086,10 @@ impl Hud {
.controls .controls
.get_binding(GameInput::ToggleDebug) .get_binding(GameInput::ToggleDebug)
{ {
Text::new( Text::new(&i18n.get("hud.press_key_to_toggle_debug_info_fmt").replace(
&i18n "{key}",
.get("hud.press_key_to_toggle_debug_info_fmt") toggle_debug_key.display_string(key_layout).as_str(),
.replace("{key}", toggle_debug_key.to_string().as_str()), ))
)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.help_info, 5.0) .down_from(self.ids.help_info, 5.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -2102,7 +2102,7 @@ impl Hud {
Text::new( Text::new(
&i18n &i18n
.get("hud.press_key_to_show_keybindings_fmt") .get("hud.press_key_to_show_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()), .replace("{key}", help_key.display_string(key_layout).as_str()),
) )
.color(TEXT_COLOR) .color(TEXT_COLOR)
.bottom_left_with_margins_on(ui_widgets.window, 210.0, 10.0) .bottom_left_with_margins_on(ui_widgets.window, 210.0, 10.0)
@ -2116,11 +2116,10 @@ impl Hud {
.controls .controls
.get_binding(GameInput::ToggleDebug) .get_binding(GameInput::ToggleDebug)
{ {
Text::new( Text::new(&i18n.get("hud.press_key_to_show_debug_info_fmt").replace(
&i18n "{key}",
.get("hud.press_key_to_show_debug_info_fmt") toggle_debug_key.display_string(key_layout).as_str(),
.replace("{key}", toggle_debug_key.to_string().as_str()), ))
)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -2133,11 +2132,10 @@ impl Hud {
.controls .controls
.get_binding(GameInput::ToggleLantern) .get_binding(GameInput::ToggleLantern)
{ {
Text::new( Text::new(&i18n.get("hud.press_key_to_toggle_lantern_fmt").replace(
&i18n "{key}",
.get("hud.press_key_to_toggle_lantern_fmt") toggle_lantern_key.display_string(key_layout).as_str(),
.replace("{key}", toggle_lantern_key.to_string().as_str()), ))
)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.up_from(self.ids.help_info, 2.0) .up_from(self.ids.help_info, 2.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -2259,6 +2257,7 @@ impl Hud {
&global_state.i18n, &global_state.i18n,
&global_state.settings, &global_state.settings,
&prompt_dialog_settings, &prompt_dialog_settings,
&global_state.window.key_layout,
) )
.set(self.ids.prompt_dialog, ui_widgets) .set(self.ids.prompt_dialog, ui_widgets)
{ {
@ -2894,7 +2893,7 @@ impl Hud {
if self.show.free_look { if self.show.free_look {
let msg = i18n let msg = i18n
.get("hud.free_look_indicator") .get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()); .replace("{key}", freelook_key.display_string(key_layout).as_str());
Text::new(&msg) Text::new(&msg)
.color(TEXT_BG) .color(TEXT_BG)
.mid_top_with_margin_on(ui_widgets.window, indicator_offset) .mid_top_with_margin_on(ui_widgets.window, indicator_offset)
@ -2937,7 +2936,7 @@ impl Hud {
if self.show.camera_clamp { if self.show.camera_clamp {
let msg = i18n let msg = i18n
.get("hud.camera_clamp_indicator") .get("hud.camera_clamp_indicator")
.replace("{key}", cameraclamp_key.to_string().as_str()); .replace("{key}", cameraclamp_key.display_string(key_layout).as_str());
Text::new(&msg) Text::new(&msg)
.color(TEXT_BG) .color(TEXT_BG)
.mid_top_with_margin_on(ui_widgets.window, indicator_offset) .mid_top_with_margin_on(ui_widgets.window, indicator_offset)

View File

@ -10,6 +10,7 @@ use conrod_core::{
use std::borrow::Cow; use std::borrow::Cow;
pub const TEXT_COLOR: Color = Color::Rgba(0.61, 0.61, 0.89, 1.0); pub const TEXT_COLOR: Color = Color::Rgba(0.61, 0.61, 0.89, 1.0);
use keyboard_keynames::key_layout::KeyLayout;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
@ -34,6 +35,7 @@ pub struct Overitem<'a> {
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
active: bool, active: bool,
key_layout: &'a Option<KeyLayout>,
} }
impl<'a> Overitem<'a> { impl<'a> Overitem<'a> {
@ -44,6 +46,7 @@ impl<'a> Overitem<'a> {
fonts: &'a Fonts, fonts: &'a Fonts,
controls: &'a ControlSettings, controls: &'a ControlSettings,
active: bool, active: bool,
key_layout: &'a Option<KeyLayout>,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -53,6 +56,7 @@ impl<'a> Overitem<'a> {
controls, controls,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
active, active,
key_layout,
} }
} }
} }
@ -143,7 +147,7 @@ impl<'a> Widget for Overitem<'a> {
.depth(self.distance_from_player_sqr + 1.0) .depth(self.distance_from_player_sqr + 1.0)
.parent(id) .parent(id)
.set(state.ids.btn_bg, ui); .set(state.ids.btn_bg, ui);
Text::new(&format!("{}", key_button)) Text::new(key_button.display_string(self.key_layout).as_str())
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(btn_font_size as u32) .font_size(btn_font_size as u32)
.color(TEXT_COLOR) .color(TEXT_COLOR)

View File

@ -11,6 +11,7 @@ use conrod_core::{
widget::{self, Button, Image, Text}, widget::{self, Button, Image, Text},
widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use keyboard_keynames::key_layout::KeyLayout;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
@ -34,6 +35,7 @@ pub struct PromptDialog<'a> {
localized_strings: &'a AssetHandle<Localization>, localized_strings: &'a AssetHandle<Localization>,
settings: &'a Settings, settings: &'a Settings,
prompt_dialog_settings: &'a PromptDialogSettings, prompt_dialog_settings: &'a PromptDialogSettings,
key_layout: &'a Option<KeyLayout>,
} }
impl<'a> PromptDialog<'a> { impl<'a> PromptDialog<'a> {
@ -44,6 +46,7 @@ impl<'a> PromptDialog<'a> {
localized_strings: &'a AssetHandle<Localization>, localized_strings: &'a AssetHandle<Localization>,
settings: &'a Settings, settings: &'a Settings,
prompt_dialog_settings: &'a PromptDialogSettings, prompt_dialog_settings: &'a PromptDialogSettings,
key_layout: &'a Option<KeyLayout>,
) -> Self { ) -> Self {
Self { Self {
imgs, imgs,
@ -52,6 +55,7 @@ impl<'a> PromptDialog<'a> {
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
settings, settings,
prompt_dialog_settings, prompt_dialog_settings,
key_layout,
} }
} }
} }
@ -88,12 +92,12 @@ impl<'a> Widget for PromptDialog<'a> {
.settings .settings
.controls .controls
.get_binding(GameInput::AcceptGroupInvite) .get_binding(GameInput::AcceptGroupInvite)
.map_or_else(|| "".into(), |key| key.to_string()); .map_or_else(|| "".into(), |key| key.display_string(self.key_layout));
let decline_key = self let decline_key = self
.settings .settings
.controls .controls
.get_binding(GameInput::DeclineGroupInvite) .get_binding(GameInput::DeclineGroupInvite)
.map_or_else(|| "".into(), |key| key.to_string()); .map_or_else(|| "".into(), |key| key.display_string(self.key_layout));
// Window // Window
Image::new(self.imgs.prompt_top) Image::new(self.imgs.prompt_top)

View File

@ -74,6 +74,7 @@ impl<'a> Widget for Controls<'a> {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let mut events = Vec::new(); let mut events = Vec::new();
let key_layout = &self.global_state.window.key_layout;
Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT) Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT)
.xy(args.rect.xy()) .xy(args.rect.xy())
@ -125,7 +126,7 @@ impl<'a> Widget for Controls<'a> {
) )
} else if let Some(key) = controls.get_binding(game_input) { } else if let Some(key) = controls.get_binding(game_input) {
( (
key.to_string(), key.display_string(key_layout),
if controls.has_conflicting_bindings(key) { if controls.has_conflicting_bindings(key) {
TEXT_BIND_CONFLICT_COLOR TEXT_BIND_CONFLICT_COLOR
} else { } else {

View File

@ -245,6 +245,7 @@ impl<'a> Widget for Skillbar<'a> {
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
let localized_strings = self.localized_strings; let localized_strings = self.localized_strings;
let key_layout = &self.global_state.window.key_layout;
let slot_offset = 3.0; let slot_offset = 3.0;
@ -265,7 +266,7 @@ impl<'a> Widget for Skillbar<'a> {
Text::new( Text::new(
&localized_strings &localized_strings
.get("hud.press_key_to_respawn") .get("hud.press_key_to_respawn")
.replace("{key}", key.to_string().as_str()), .replace("{key}", key.display_string(key_layout).as_str()),
) )
.mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0) .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
.font_size(self.fonts.cyri.scale(30)) .font_size(self.fonts.cyri.scale(30))
@ -281,7 +282,7 @@ impl<'a> Widget for Skillbar<'a> {
Text::new( Text::new(
&localized_strings &localized_strings
.get("hud.press_key_to_respawn") .get("hud.press_key_to_respawn")
.replace("{key}", key.to_string().as_str()), .replace("{key}", key.display_string(key_layout).as_str()),
) )
.bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0) .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(30)) .font_size(self.fonts.cyri.scale(30))
@ -825,13 +826,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot1) .get_binding(GameInput::Slot1)
{ {
Text::new(slot1.to_string().as_str()) Text::new(slot1.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot1, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot1, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot1_text_bg, ui); .set(state.ids.slot1_text_bg, ui);
Text::new(slot1.to_string().as_str()) Text::new(slot1.display_string(key_layout).as_str())
.bottom_left_with_margins_on(state.ids.slot1_text_bg, 1.0, 1.0) .bottom_left_with_margins_on(state.ids.slot1_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -844,13 +845,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot2) .get_binding(GameInput::Slot2)
{ {
Text::new(slot2.to_string().as_str()) Text::new(slot2.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot2, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot2, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot2_text_bg, ui); .set(state.ids.slot2_text_bg, ui);
Text::new(slot2.to_string().as_str()) Text::new(slot2.display_string(key_layout).as_str())
.bottom_left_with_margins_on(state.ids.slot2_text_bg, 1.0, 1.0) .bottom_left_with_margins_on(state.ids.slot2_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -863,13 +864,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot3) .get_binding(GameInput::Slot3)
{ {
Text::new(slot3.to_string().as_str()) Text::new(slot3.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot3, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot3, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot3_text_bg, ui); .set(state.ids.slot3_text_bg, ui);
Text::new(slot3.to_string().as_str()) Text::new(slot3.display_string(key_layout).as_str())
.bottom_left_with_margins_on(state.ids.slot3_text_bg, 1.0, 1.0) .bottom_left_with_margins_on(state.ids.slot3_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -882,13 +883,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot4) .get_binding(GameInput::Slot4)
{ {
Text::new(slot4.to_string().as_str()) Text::new(slot4.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot4, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot4, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot4_text_bg, ui); .set(state.ids.slot4_text_bg, ui);
Text::new(slot4.to_string().as_str()) Text::new(slot4.display_string(key_layout).as_str())
.bottom_left_with_margins_on(state.ids.slot4_text_bg, 1.0, 1.0) .bottom_left_with_margins_on(state.ids.slot4_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -901,13 +902,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot5) .get_binding(GameInput::Slot5)
{ {
Text::new(slot5.to_string().as_str()) Text::new(slot5.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot5, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot5, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot5_text_bg, ui); .set(state.ids.slot5_text_bg, ui);
Text::new(slot5.to_string().as_str()) Text::new(slot5.display_string(key_layout).as_str())
.bottom_left_with_margins_on(state.ids.slot5_text_bg, 1.0, 1.0) .bottom_left_with_margins_on(state.ids.slot5_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -920,13 +921,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot6) .get_binding(GameInput::Slot6)
{ {
Text::new(slot6.to_string().as_str()) Text::new(slot6.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot6, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot6, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot6_text_bg, ui); .set(state.ids.slot6_text_bg, ui);
Text::new(slot6.to_string().as_str()) Text::new(slot6.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.slot6_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.slot6_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -939,13 +940,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot7) .get_binding(GameInput::Slot7)
{ {
Text::new(slot7.to_string().as_str()) Text::new(slot7.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot7, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot7, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot7_text_bg, ui); .set(state.ids.slot7_text_bg, ui);
Text::new(slot7.to_string().as_str()) Text::new(slot7.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.slot7_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.slot7_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -958,13 +959,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot8) .get_binding(GameInput::Slot8)
{ {
Text::new(slot8.to_string().as_str()) Text::new(slot8.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot8, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot8, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot8_text_bg, ui); .set(state.ids.slot8_text_bg, ui);
Text::new(slot8.to_string().as_str()) Text::new(slot8.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.slot8_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.slot8_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -977,13 +978,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot9) .get_binding(GameInput::Slot9)
{ {
Text::new(slot9.to_string().as_str()) Text::new(slot9.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot9, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot9, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot9_text_bg, ui); .set(state.ids.slot9_text_bg, ui);
Text::new(slot9.to_string().as_str()) Text::new(slot9.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.slot9_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.slot9_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
@ -996,13 +997,13 @@ impl<'a> Widget for Skillbar<'a> {
.controls .controls
.get_binding(GameInput::Slot10) .get_binding(GameInput::Slot10)
{ {
Text::new(slot10.to_string().as_str()) Text::new(slot10.display_string(key_layout).as_str())
.top_right_with_margins_on(state.ids.slot10, 3.0, 5.0) .top_right_with_margins_on(state.ids.slot10, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.set(state.ids.slot10_text_bg, ui); .set(state.ids.slot10_text_bg, ui);
Text::new(slot10.to_string().as_str()) Text::new(slot10.display_string(key_layout).as_str())
.bottom_right_with_margins_on(state.ids.slot10_text_bg, 1.0, 1.0) .bottom_right_with_margins_on(state.ids.slot10_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8)) .font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)

View File

@ -9,9 +9,9 @@ use crossbeam::channel;
use gilrs::{EventType, Gilrs}; use gilrs::{EventType, Gilrs};
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use keyboard_keynames::key_layout::KeyLayout;
use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use vek::*; use vek::*;
use winit::monitor::VideoMode; use winit::monitor::VideoMode;
@ -320,11 +320,11 @@ pub enum KeyMouse {
ScanKey(winit::event::ScanCode), ScanKey(winit::event::ScanCode),
} }
impl fmt::Display for KeyMouse { impl KeyMouse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub fn display_string(&self, key_layout: &Option<KeyLayout>) -> String {
use self::KeyMouse::*; use self::KeyMouse::*;
use winit::event::{MouseButton, VirtualKeyCode::*}; use winit::event::{MouseButton, VirtualKeyCode::*};
write!(f, "{}", match self { let key_string = match self {
Key(Key1) => "1", Key(Key1) => "1",
Key(Key2) => "2", Key(Key2) => "2",
Key(Key3) => "3", Key(Key3) => "3",
@ -491,11 +491,20 @@ impl fmt::Display for KeyMouse {
Mouse(MouseButton::Left) => "M1", Mouse(MouseButton::Left) => "M1",
Mouse(MouseButton::Right) => "M2", Mouse(MouseButton::Right) => "M2",
Mouse(MouseButton::Middle) => "M3", Mouse(MouseButton::Middle) => "M3",
Mouse(MouseButton::Other(button)) => Mouse(MouseButton::Other(button)) => {
// Additional mouse buttons after middle click start at 1 // Additional mouse buttons after middle click start at 1
return write!(f, "M{}", button + 3), return format!("M{}", button + 3);
ScanKey(_) => "Unknown", },
}) ScanKey(scancode) => {
if let Some(layout) = key_layout {
return layout.get_key_as_string(*scancode);
} else {
return format!("Unknown({})", scancode);
}
},
};
String::from(key_string)
} }
} }
@ -525,6 +534,7 @@ pub struct Window {
// Used for screenshots & fullscreen toggle to deduplicate/postpone to after event handler // Used for screenshots & fullscreen toggle to deduplicate/postpone to after event handler
take_screenshot: bool, take_screenshot: bool,
toggle_fullscreen: bool, toggle_fullscreen: bool,
pub key_layout: Option<KeyLayout>,
} }
impl Window { impl Window {
@ -600,6 +610,18 @@ impl Window {
let scale_factor = window.window().scale_factor(); let scale_factor = window.window().scale_factor();
let key_layout = match KeyLayout::new_from_window(window.window()) {
Ok(kl) => Some(kl),
Err(err) => {
warn!(
?err,
"Failed to construct the scancode to keyname mapper, falling back to \
displaying Unknown(<scancode>)."
);
None
},
};
let mut this = Self { let mut this = Self {
renderer: Renderer::new( renderer: Renderer::new(
device, device,
@ -631,6 +653,7 @@ impl Window {
message_receiver, message_receiver,
take_screenshot: false, take_screenshot: false,
toggle_fullscreen: false, toggle_fullscreen: false,
key_layout,
}; };
this.set_fullscreen_mode(settings.graphics.fullscreen); this.set_fullscreen_mode(settings.graphics.fullscreen);