Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions EmoTracker.Data/ApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,39 @@ internal void SeedIntoSession(Sessions.SessionSettings target)
target.PinLocationsOnItemCapture = mbSeedPinLocationsOnItemCapture;
}

/// <summary>
/// Companion to <see cref="SeedIntoSession"/>, called by
/// <see cref="Sessions.SessionSettings"/>'s OnChanged hooks when a
/// forwarded setting is written directly on a state (menu bindings,
/// dev terminal, MCP tools, Lua). If the source is the active
/// state's settings, updates the persisted seed — unless the write
/// came from pack-load (init.lua), per
/// <see cref="Sessions.PackageLoader.IsLoading"/> — and raises
/// PropertyChanged so XAML bound to this singleton's forwarder
/// properties updates even though the write bypassed the forwarder
/// setter. Writes on non-active states (forks mid-pipeline,
/// definitional states) are ignored.
/// </summary>
internal void SyncSeedsFromSession(Sessions.SessionSettings source, string propertyName)
{
if (source == null || !ReferenceEquals(source, ActiveSessionSettings))
return;

if (!Sessions.PackageLoader.IsLoading)
{
switch (propertyName)
{
case nameof(IgnoreAllLogic): mbSeedIgnoreAllLogic = source.IgnoreAllLogic; break;
case nameof(DisplayAllLocations): mbSeedDisplayAllLocations = source.DisplayAllLocations; break;
case nameof(AlwaysAllowClearing): mbSeedAlwaysAllowClearing = source.AlwaysAllowClearing; break;
case nameof(AutoUnpinLocationsOnClear): mbSeedAutoUnpinLocationsOnClear = source.AutoUnpinLocationsOnClear; break;
case nameof(PinLocationsOnItemCapture): mbSeedPinLocationsOnItemCapture = source.PinLocationsOnItemCapture; break;
}
}

NotifyPropertyChanged(propertyName);
}

public bool IgnoreAllLogic
{
get
Expand Down
58 changes: 58 additions & 0 deletions EmoTracker.Data/Sessions/SessionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,27 @@ public partial class SessionSettings : TransactableModelTypeBase
public partial bool IgnoreAllLogic { get; set; }

[KVMutable]
[OnChanged(nameof(OnDisplayAllLocationsChanged))]
public partial bool DisplayAllLocations { get; set; }

[KVMutable]
[OnChanged(nameof(OnAlwaysAllowClearingChanged))]
public partial bool AlwaysAllowClearing { get; set; }

[KVMutable]
[OnChanged(nameof(OnAutoUnpinLocationsOnClearChanged))]
public partial bool AutoUnpinLocationsOnClear { get; set; }

[KVMutable]
[OnChanged(nameof(OnPinLocationsOnItemCaptureChanged))]
public partial bool PinLocationsOnItemCapture { get; set; }

[KVMutable]
[OnChanged(nameof(OnMapEnabledChanged))]
public partial bool MapEnabled { get; set; }

[KVMutable]
[OnChanged(nameof(OnSwapLeftRightChanged))]
public partial bool SwapLeftRight { get; set; }

// ----------- Construction ---------------------------------------------
Expand Down Expand Up @@ -103,6 +109,22 @@ public SessionSettings()

// ----------- OnChanged hooks ------------------------------------------

/// <summary>
/// Set by <see cref="TrackerState.Fork"/> around its settings
/// bulk-copy so propagating values into a fork doesn't fire
/// side-effectful OnChanged hooks (e.g. a pack reload) against the
/// half-built fork state.
/// </summary>
internal bool SuppressOnChangedHooks;

protected void OnSwapLeftRightChanged() => ReloadOwnerState();
protected void OnMapEnabledChanged() => ReloadOwnerState();

protected void OnDisplayAllLocationsChanged() => SyncForwarder(nameof(DisplayAllLocations));
protected void OnAlwaysAllowClearingChanged() => SyncForwarder(nameof(AlwaysAllowClearing));
protected void OnAutoUnpinLocationsOnClearChanged() => SyncForwarder(nameof(AutoUnpinLocationsOnClear));
protected void OnPinLocationsOnItemCaptureChanged() => SyncForwarder(nameof(PinLocationsOnItemCapture));

protected void OnIgnoreAllLogicChanged()
{
// Drive a refresh on the owning state's LocationDatabase so the
Expand All @@ -111,6 +133,42 @@ protected void OnIgnoreAllLogicChanged()
// ApplicationSettings.IgnoreAllLogic's setter.
var state = OwnerState as TrackerState;
state?.Locations.RefreshAccessibility();
SyncForwarder(nameof(IgnoreAllLogic));
}

/// <summary>
/// SwapLeftRight and MapEnabled are consumed at pack parse time
/// (<c>LayoutItem.TryParse</c> mirrors dock/margin definition
/// values; <c>MapPanel.TryParseInternal</c> bails when the map is
/// disabled), so a new value only takes effect after re-parsing
/// the pack — the legacy <c>Tracker</c> setters called
/// <c>Reload()</c> for the same reason. No pack loaded means
/// nothing to re-parse, so skip rather than pointlessly resetting
/// empty catalogs.
/// </summary>
void ReloadOwnerState()
{
if (SuppressOnChangedHooks) return;
var state = OwnerState as TrackerState;
if (state?.PackageInstance?.GamePackage == null) return;
state.Reload();
}

/// <summary>
/// <see cref="ApplicationSettings"/> keeps forwarder properties
/// (plus seeds persisted to ApplicationSettings.json) for code and
/// XAML bound to the process-wide singleton rather than a state's
/// Settings (e.g. LocationMapControl's DisplayAllLocations
/// multibinding). Writes through this state's setters must fan out
/// to the singleton or those bindings go stale — that asymmetry is
/// why the "Show All Locations" menu toggle (bound per-state) had
/// no visible effect while the F11 hotkey (routed through the
/// forwarder) worked.
/// </summary>
void SyncForwarder(string propertyName)
{
if (SuppressOnChangedHooks) return;
ApplicationSettings.Instance.SyncSeedsFromSession(this, propertyName);
}

// ----------- Fork support ---------------------------------------------
Expand Down
26 changes: 19 additions & 7 deletions EmoTracker.Data/Sessions/TrackerState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -505,13 +505,25 @@ public TrackerState Fork(string name = null)
// store via InitializeAsForkOf — but tying the lifecycle to
// ItemBase.Fork's machinery here is more boilerplate than just
// copying the seven scalar bools, so we bulk-copy directly.
copy.Settings.IgnoreAllLogic = this.Settings.IgnoreAllLogic;
copy.Settings.DisplayAllLocations = this.Settings.DisplayAllLocations;
copy.Settings.AlwaysAllowClearing = this.Settings.AlwaysAllowClearing;
copy.Settings.AutoUnpinLocationsOnClear = this.Settings.AutoUnpinLocationsOnClear;
copy.Settings.PinLocationsOnItemCapture = this.Settings.PinLocationsOnItemCapture;
copy.Settings.MapEnabled = this.Settings.MapEnabled;
copy.Settings.SwapLeftRight = this.Settings.SwapLeftRight;
// Hooks are suppressed for the copy: the fork's layout tree was
// forked above from the source's already-mirrored shape, so
// SwapLeftRight's reload-on-change hook must not re-run pack
// load against the half-built fork.
copy.Settings.SuppressOnChangedHooks = true;
try
{
copy.Settings.IgnoreAllLogic = this.Settings.IgnoreAllLogic;
copy.Settings.DisplayAllLocations = this.Settings.DisplayAllLocations;
copy.Settings.AlwaysAllowClearing = this.Settings.AlwaysAllowClearing;
copy.Settings.AutoUnpinLocationsOnClear = this.Settings.AutoUnpinLocationsOnClear;
copy.Settings.PinLocationsOnItemCapture = this.Settings.PinLocationsOnItemCapture;
copy.Settings.MapEnabled = this.Settings.MapEnabled;
copy.Settings.SwapLeftRight = this.Settings.SwapLeftRight;
}
finally
{
copy.Settings.SuppressOnChangedHooks = false;
}

// ---- Per-state pack-driven scalar settings ---------------------
// AllowResize / DisabledImageFilterSpec are populated on the
Expand Down