Skip to content

Commit

Permalink
feat(layouts): override layout anchor points with user settings (#108)
Browse files Browse the repository at this point in the history
Players can now choose to override a layout's chosen anchor point
with their own preferred HUD location. This won't work well for
every layout, but players can decide that for themselves.

Added the new option to the settings struct and MCM config. It's an 
enum that allows players to choose from the current list of anchor 
names. Added new translation strings to support the config.

`apply_settings()` now refreshes the layout to pick up any
location changes.

Added a test to ensure that the setting is respected by the
anchor_point() function if set. This bottleneck ensures that
flattened layouts get the overridden location automatically.
To support the test, added a way to specific a refresh-from file
for the settings lazy static. The new convenience functions to
Layout that match on its variants are also there to support tests.
  • Loading branch information
ceejbot authored Jan 12, 2024
1 parent 1a53b78 commit 901629d
Show file tree
Hide file tree
Showing 18 changed files with 784 additions and 621 deletions.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_czech.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_english.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_french.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_german.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_italian.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_japanese.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_polish.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_russian.txt
Binary file not shown.
Binary file modified installer/core/Interface/Translations/SoulsyHUD_spanish.txt
Binary file not shown.
1,256 changes: 639 additions & 617 deletions installer/core/mcm/config/SoulsyHUD/config.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions installer/core/mcm/config/SoulsyHUD/settings.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ bGroupPotions = 0
bCycleAmmo = 1
bColorizeIcons = 1
bEquipSetsUnequip = 1
uAnchorLocation = none
sSKSEIdentifier = SOLS
bDebugMode = 0
sLogLevel = info
Expand Down
3 changes: 3 additions & 0 deletions src/controller/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ impl Controller {
}
}

// Apply any new anchor relocations to the current layout.
Layout::refresh();

self.cache.introspect();
}

Expand Down
43 changes: 42 additions & 1 deletion src/controller/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use once_cell::sync::Lazy;
use strum::Display;

use super::keys::Hotkey;
use crate::plugin::HudElement;
use crate::{layouts::shared::NamedAnchor, plugin::HudElement};

/// This is the path to players's modified settings.
static SETTINGS_PATH: &str = "./data/MCM/Settings/SoulsyHUD.ini";
Expand Down Expand Up @@ -100,6 +100,8 @@ pub struct UserSettings {
showhide: u32,
/// A hotkey for re-reading the layout from toml and redrawing. uRefreshKey
refresh_layout: u32,
/// Layout anchor override. uAnchorLocation
anchor_loc: NamedAnchor,

/// The number of milliseconds to delay before equipping a selection. Max 2500, min 0.
equip_delay_ms: u32,
Expand Down Expand Up @@ -141,6 +143,7 @@ impl Default for UserSettings {
right: 7,
equipset: 9,
refresh_layout: 8,
anchor_loc: NamedAnchor::None,
how_to_activate: ActivationMethod::Hotkey,
activate: 4,
activate_modifier: -1,
Expand Down Expand Up @@ -184,6 +187,13 @@ impl UserSettings {
settings.read_from_file(SETTINGS_PATH)
}

pub fn refresh_with(fpath: &str) -> Result<()> {
let mut settings = SETTINGS
.lock()
.expect("Unrecoverable runtime problem: cannot acquire settings lock.");
settings.read_from_file(fpath)
}

/// Refresh ourselves from the MCM-controlled file.
pub fn read_from_file(&mut self, fpath: &str) -> Result<()> {
// We'll fall back to defaults at a different level.
Expand Down Expand Up @@ -231,6 +241,7 @@ impl UserSettings {

self.showhide = read_from_ini(self.showhide, "uShowHideKey", controls);
self.refresh_layout = read_from_ini(self.refresh_layout, "uRefreshKey", controls);
self.anchor_loc = read_from_ini(self.anchor_loc.clone(), "uAnchorLocation", options);

self.unarmed_handling = read_from_ini(self.unarmed_handling, "uHowToUnequip", controls);
self.unequip_modifier =
Expand Down Expand Up @@ -404,6 +415,9 @@ impl UserSettings {
pub fn refresh_layout(&self) -> u32 {
self.refresh_layout
}
pub fn anchor_loc(&self) -> &NamedAnchor {
&self.anchor_loc
}
pub fn maxlen(&self) -> u32 {
20
}
Expand Down Expand Up @@ -591,6 +605,31 @@ impl FromIniStr for String {
}
}

impl FromIniStr for NamedAnchor {
fn from_ini(value: &str) -> Option<Self>
where
Self: Sized,
{
if let Ok(v) = value.parse::<i32>() {
match v {
0 => Some(NamedAnchor::None),
1 => Some(NamedAnchor::TopLeft),
2 => Some(NamedAnchor::TopRight),
3 => Some(NamedAnchor::BottomLeft),
4 => Some(NamedAnchor::BottomRight),
5 => Some(NamedAnchor::Center),
6 => Some(NamedAnchor::CenterTop),
7 => Some(NamedAnchor::CenterBottom),
8 => Some(NamedAnchor::LeftCenter),
9 => Some(NamedAnchor::LeftCenter),
_ => Some(NamedAnchor::None),
}
} else {
None
}
}
}

// qualified so we don't collide with the macro strum::Display
// We implement this so the logs contain a human-readable dump of the settings
// at save game load, so people can debug.
Expand All @@ -606,6 +645,7 @@ impl std::fmt::Display for UserSettings {
right cycle key: {}
equipset cycle key: {}
refresh layout key: {}
layout anchor override: {}
how_to_activate: {}
activate consumables: {}
activate_modifier: {}
Expand Down Expand Up @@ -638,6 +678,7 @@ impl std::fmt::Display for UserSettings {
self.right,
self.equipset,
self.refresh_layout,
self.anchor_loc,
self.how_to_activate,
self.activate,
self.activate_modifier,
Expand Down
43 changes: 41 additions & 2 deletions src/layouts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize};
use self::shared::NamedAnchor;
use crate::control::notify;
use crate::controller::control::translated_key;
use crate::controller::user_settings;
use crate::plugin::{LayoutFlattened, Point};

static LAYOUT_PATH: &str = "./data/SKSE/Plugins/SoulsyHUD_Layout.toml";
Expand All @@ -35,7 +36,7 @@ pub fn hud_layout() -> LayoutFlattened {
layout.clone()
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Layout {
Version1(Box<HudLayout1>),
Expand Down Expand Up @@ -121,6 +122,13 @@ impl Layout {
Layout::Version2(v) => LayoutFlattened::from(&**v),
}
}

pub fn anchor_point(&self) -> Point {
match self {
Layout::Version1(v) => v.anchor_point(),
Layout::Version2(v) => v.anchor_point(),
}
}
}

pub fn anchor_point(
Expand All @@ -137,7 +145,16 @@ pub fn anchor_point(
let width = size.x * global_scale;
let height = size.y * global_scale;

match anchor_name {
let config = *user_settings();
let user_pref_anchor = config.anchor_loc();
eprintln!("{user_pref_anchor}");
let anchor_to_use = if !matches!(user_pref_anchor, &NamedAnchor::None) {
user_pref_anchor
} else {
anchor_name
};

match anchor_to_use {
NamedAnchor::TopLeft => Point {
x: width / 2.0,
y: height / 2.0,
Expand Down Expand Up @@ -322,4 +339,26 @@ mod tests {
);
assert_eq!(pointed.anchor_point(), named.anchor_point());
}

#[test]
fn anchor_points_respect_settings() {
// override the defaults with what we need for this test
let _ = crate::controller::UserSettings::refresh_with("tests/fixtures/test-settings.ini");

let named = Layout::read_from_file("tests/fixtures/named-anchor.toml")
.expect("this test fixture exists and is valid");
// this layout has bottom left as an anchor point
// the test settings override with "center"
let relocated = named.anchor_point();
assert_eq!(
relocated,
Point {
x: 1720.0,
y: 720.0
}
);

let flattened = named.flatten();
assert_eq!(flattened.anchor, relocated);
}
}
22 changes: 22 additions & 0 deletions src/layouts/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,28 @@ where
}
}

impl From<&str> for NamedAnchor {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"top_left" => NamedAnchor::TopLeft,
"top_right" => NamedAnchor::TopRight,
"bottom_left" => NamedAnchor::BottomLeft,
"bottom_right" => NamedAnchor::BottomRight,
"center" => NamedAnchor::Center,
"center_top" => NamedAnchor::CenterTop,
"top_center" => NamedAnchor::CenterTop,
"center_bottom" => NamedAnchor::CenterBottom,
"bottom_center" => NamedAnchor::CenterBottom,
"left_center" => NamedAnchor::LeftCenter,
"center_left" => NamedAnchor::LeftCenter,
"right_center" => NamedAnchor::RightCenter,
"center_right" => NamedAnchor::RightCenter,
"none" => NamedAnchor::None,
_ => NamedAnchor::None,
}
}
}

// ---------- MeterType

// We can't derive this because it is exposed to C++.
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/all-types.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bBooleanStringF = false
bBooleanEmpty =
sString = String with spaces
sStringEmpty =
uNamedAnchor = "top_left"

[emptysection]

Expand Down
34 changes: 34 additions & 0 deletions tests/fixtures/test-settings.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[Options]
bCycleAmmo = 1
bAutoFade = 1
bGroupPotions = 1
bDebugMode = 1
sSKSEIdentifier = WOMP
bLinkToFavorites = 1
bEquipSetsUnequip = 1
sLogLevel = debug
bCyclingSlowsTime = 1
uFadeTime = 1500
uEquipDelay = 2500
uLongPressMillis = 2750
uAnchorLocation = 5

[Controls]
uRefreshKey = 8
uShowHideKey = 2
uHowToUnequip = 0
bLongPressMatches = 1
iUnequipModifierKey = 184
uPowerCycleKey = 3
uLeftCycleKey = 5
uRightCycleKey = 7
uUtilityCycleKey = 6
uHowToActivate = 0
uHowToggleInMenus = 0
iMenuModifierKey = 42
iUtilityActivateModifier = 274


[Equipsets]
sLastEditedSetName = Mossy
sLastUsedSetName = Spiky

0 comments on commit 901629d

Please sign in to comment.