From 380d30d63fbf4fad2398f5970d9118cbc2d7a556 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Wed, 4 Oct 2023 11:40:55 -0400
Subject: [PATCH] Rewrote code for preferences to use Save data.

---
 source/Main.hx                           |  16 +--
 source/funkin/InitState.hx               |   2 +-
 source/funkin/MainMenuState.hx           |  18 +--
 source/funkin/Preferences.hx             | 138 ++++++++++++++++++++
 source/funkin/audiovis/ABotVis.hx        |   1 -
 source/funkin/import.hx                  |   1 +
 source/funkin/play/GameOverSubState.hx   |   3 +-
 source/funkin/play/PlayState.hx          |   6 +-
 source/funkin/play/notes/Strumline.hx    |  14 +-
 source/funkin/play/notes/SustainTrail.hx |   2 +-
 source/funkin/save/Save.hx               |  28 +++-
 source/funkin/ui/PreferencesMenu.hx      | 157 +++++++----------------
 source/funkin/ui/TextMenuList.hx         |   6 +-
 source/funkin/util/Constants.hx          |   3 +
 14 files changed, 237 insertions(+), 158 deletions(-)
 create mode 100644 source/funkin/Preferences.hx

diff --git a/source/Main.hx b/source/Main.hx
index 8419d3fb4..dffe666b7 100644
--- a/source/Main.hx
+++ b/source/Main.hx
@@ -85,6 +85,13 @@ class Main extends Sprite
 
     initHaxeUI();
 
+    fpsCounter = new FPS(10, 3, 0xFFFFFF);
+    // addChild(fpsCounter); // Handled by Preferences.init
+    #if !html5
+    memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
+    // addChild(memoryCounter);
+    #end
+
     // George recommends binding the save before FlxGame is created.
     Save.load();
 
@@ -93,15 +100,6 @@ class Main extends Sprite
     #if hxcpp_debug_server
     trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
     #end
-
-    #if debug
-    fpsCounter = new FPS(10, 3, 0xFFFFFF);
-    addChild(fpsCounter);
-    #if !html5
-    memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
-    addChild(memoryCounter);
-    #end
-    #end
   }
 
   function initHaxeUI():Void
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 0a7d413c1..ecfa32eb3 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -48,7 +48,7 @@ class InitState extends FlxState
 
     // loadSaveData(); // Moved to Main.hx
     // Load player options from save data.
-    PreferencesMenu.initPrefs();
+    Preferences.init();
     // Load controls from save data.
     PlayerSettings.init();
 
diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx
index 7c54357bb..7267a6da8 100644
--- a/source/funkin/MainMenuState.hx
+++ b/source/funkin/MainMenuState.hx
@@ -13,23 +13,13 @@ import flixel.input.touch.FlxTouch;
 import flixel.text.FlxText;
 import flixel.tweens.FlxEase;
 import flixel.tweens.FlxTween;
-import flixel.util.FlxColor;
 import flixel.util.FlxTimer;
-import funkin.NGio;
-import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
-import funkin.modding.module.ModuleHandler;
-import funkin.shaderslmfao.ScreenWipeShader;
 import funkin.ui.AtlasMenuList;
-import funkin.ui.MenuList.MenuItem;
 import funkin.ui.MenuList;
 import funkin.ui.title.TitleState;
 import funkin.ui.story.StoryMenuState;
-import funkin.ui.OptionsState;
-import funkin.ui.PreferencesMenu;
 import funkin.ui.Prompt;
 import funkin.util.WindowUtil;
-import lime.app.Application;
-import openfl.filters.ShaderFilter;
 #if discord_rpc
 import Discord.DiscordClient;
 #end
@@ -82,8 +72,10 @@ class MainMenuState extends MusicBeatState
     magenta.y = bg.y;
     magenta.visible = false;
     magenta.color = 0xFFfd719b;
-    if (PreferencesMenu.preferences.get('flashing-menu')) add(magenta);
-    // magenta.scrollFactor.set();
+
+    // TODO: Why doesn't this line compile I'm going fucking feral
+
+    if (Preferences.flashingLights) add(magenta);
 
     menuItems = new MenuTypedList<AtlasMenuItem>();
     add(menuItems);
@@ -116,7 +108,7 @@ class MainMenuState extends MusicBeatState
     #end
 
     createMenuItem('options', 'mainmenu/options', function() {
-      startExitState(new OptionsState());
+      startExitState(new funkin.ui.OptionsState());
     });
 
     // Reset position of menu items.
diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx
new file mode 100644
index 000000000..7e3c3c6d7
--- /dev/null
+++ b/source/funkin/Preferences.hx
@@ -0,0 +1,138 @@
+package funkin;
+
+import funkin.save.Save;
+
+/**
+ * A store of user-configurable, globally relevant values.
+ */
+class Preferences
+{
+  /**
+   * Whether some particularly fowl language is displayed.
+   * @default `true`
+   */
+  public static var naughtyness(get, set):Bool;
+
+  static function get_naughtyness():Bool
+  {
+    return Save.get().options.naughtyness;
+  }
+
+  static function set_naughtyness(value:Bool):Bool
+  {
+    return Save.get().options.naughtyness = value;
+  }
+
+  /**
+   * If enabled, the strumline is at the bottom of the screen rather than the top.
+   * @default `false`
+   */
+  public static var downscroll(get, set):Bool;
+
+  static function get_downscroll():Bool
+  {
+    return Save.get().options.downscroll;
+  }
+
+  static function set_downscroll(value:Bool):Bool
+  {
+    return Save.get().options.downscroll = value;
+  }
+
+  /**
+   * If disabled, flashing lights in the main menu and other areas will be less intense.
+   * @default `true`
+   */
+  public static var flashingLights(get, set):Bool;
+
+  static function get_flashingLights():Bool
+  {
+    return Save.get().options.flashingLights;
+  }
+
+  static function set_flashingLights(value:Bool):Bool
+  {
+    return Save.get().options.flashingLights = value;
+  }
+
+  /**
+   * If disabled, the camera bump synchronized to the beat.
+   * @default `false`
+   */
+  public static var zoomCamera(get, set):Bool;
+
+  static function get_zoomCamera():Bool
+  {
+    return Save.get().options.zoomCamera;
+  }
+
+  static function set_zoomCamera(value:Bool):Bool
+  {
+    return Save.get().options.zoomCamera = value;
+  }
+
+  /**
+   * If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
+   * @default `false`
+   */
+  public static var debugDisplay(get, set):Bool;
+
+  static function get_debugDisplay():Bool
+  {
+    return Save.get().options.debugDisplay;
+  }
+
+  static function set_debugDisplay(value:Bool):Bool
+  {
+    if (value != Save.get().options.debugDisplay)
+    {
+      toggleDebugDisplay(value);
+    }
+
+    return Save.get().options.debugDisplay = value;
+  }
+
+  /**
+   * If enabled, the game will automatically pause when tabbing out.
+   * @default `true`
+   */
+  public static var autoPause(get, set):Bool;
+
+  static function get_autoPause():Bool
+  {
+    return Save.get().options.autoPause;
+  }
+
+  static function set_autoPause(value:Bool):Bool
+  {
+    if (value != Save.get().options.autoPause) FlxG.autoPause = value;
+
+    return Save.get().options.autoPause = value;
+  }
+
+  public static function init():Void
+  {
+    FlxG.autoPause = Preferences.autoPause;
+    toggleDebugDisplay(Preferences.debugDisplay);
+  }
+
+  static function toggleDebugDisplay(show:Bool):Void
+  {
+    if (show)
+    {
+      // Enable the debug display.
+      FlxG.stage.addChild(Main.fpsCounter);
+      #if !html5
+      FlxG.stage.addChild(Main.memoryCounter);
+      #end
+    }
+    else
+    {
+      // Disable the debug display.
+      FlxG.stage.removeChild(Main.fpsCounter);
+      #if !html5
+      FlxG.stage.removeChild(Main.memoryCounter);
+      #end
+    }
+  }
+}
diff --git a/source/funkin/audiovis/ABotVis.hx b/source/funkin/audiovis/ABotVis.hx
index 2018a99b3..060bddcf7 100644
--- a/source/funkin/audiovis/ABotVis.hx
+++ b/source/funkin/audiovis/ABotVis.hx
@@ -7,7 +7,6 @@ import flixel.graphics.frames.FlxAtlasFrames;
 import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
 import flixel.math.FlxMath;
 import flixel.sound.FlxSound;
-import funkin.ui.PreferencesMenu.CheckboxThingie;
 
 using Lambda;
 
diff --git a/source/funkin/import.hx b/source/funkin/import.hx
index 06fe2bfa8..1c3a0fdb4 100644
--- a/source/funkin/import.hx
+++ b/source/funkin/import.hx
@@ -4,6 +4,7 @@ package;
 // Only import these when we aren't in a macro.
 import funkin.util.Constants;
 import funkin.Paths;
+import funkin.Preferences;
 import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
 
 // These are great.
diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx
index 15ed0421e..a3aeb4139 100644
--- a/source/funkin/play/GameOverSubState.hx
+++ b/source/funkin/play/GameOverSubState.hx
@@ -11,7 +11,6 @@ import funkin.modding.events.ScriptEvent;
 import funkin.modding.events.ScriptEventDispatcher;
 import funkin.play.PlayState;
 import funkin.play.character.BaseCharacter;
-import funkin.ui.PreferencesMenu;
 
 /**
  * A substate which renders over the PlayState when the player dies.
@@ -292,7 +291,7 @@ class GameOverSubState extends MusicBeatSubState
   {
     var randomCensor:Array<Int> = [];
 
-    if (PreferencesMenu.getPref('censor-naughty')) randomCensor = [1, 3, 8, 13, 17, 21];
+    if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21];
 
     FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() {
       // Once the quote ends, fade in the game over music.
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 8eff610c2..26aef9b3d 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -1257,7 +1257,7 @@ class PlayState extends MusicBeatSubState
    */
   function initHealthBar():Void
   {
-    var healthBarYPos:Float = PreferencesMenu.getPref('downscroll') ? FlxG.height * 0.1 : FlxG.height * 0.9;
+    var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
     healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar'));
     healthBarBG.screenCenter(X);
     healthBarBG.scrollFactor.set(0, 0);
@@ -1480,13 +1480,13 @@ class PlayState extends MusicBeatSubState
     // Position the player strumline on the right half of the screen
     playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style
     // playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style
-    playerStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
+    playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
     playerStrumline.zIndex = 200;
     playerStrumline.cameras = [camHUD];
 
     // Position the opponent strumline on the left half of the screen
     opponentStrumline.x = Constants.STRUMLINE_X_OFFSET;
-    opponentStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
+    opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
     opponentStrumline.zIndex = 100;
     opponentStrumline.cameras = [camHUD];
 
diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx
index 7bd6e7ae7..60b995c06 100644
--- a/source/funkin/play/notes/Strumline.hx
+++ b/source/funkin/play/notes/Strumline.hx
@@ -231,7 +231,7 @@ class Strumline extends FlxSpriteGroup
       notesVwoosh.add(note);
 
       var targetY:Float = FlxG.height + note.y;
-      if (PreferencesMenu.getPref('downscroll')) targetY = 0 - note.height;
+      if (Preferences.downscroll) targetY = 0 - note.height;
       FlxTween.tween(note, {y: targetY}, 0.5,
         {
           ease: FlxEase.expoIn,
@@ -252,7 +252,7 @@ class Strumline extends FlxSpriteGroup
       holdNotesVwoosh.add(holdNote);
 
       var targetY:Float = FlxG.height + holdNote.y;
-      if (PreferencesMenu.getPref('downscroll')) targetY = 0 - holdNote.height;
+      if (Preferences.downscroll) targetY = 0 - holdNote.height;
       FlxTween.tween(holdNote, {y: targetY}, 0.5,
         {
           ease: FlxEase.expoIn,
@@ -277,7 +277,7 @@ class Strumline extends FlxSpriteGroup
     var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
     var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
 
-    return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (PreferencesMenu.getPref('downscroll') ? 1 : -1);
+    return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
   }
 
   function updateNotes():Void
@@ -321,7 +321,7 @@ class Strumline extends FlxSpriteGroup
       note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh);
 
       // If the note is miss
-      var isOffscreen = PreferencesMenu.getPref('downscroll') ? note.y > FlxG.height : note.y < -note.height;
+      var isOffscreen = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
       if (note.handledMiss && isOffscreen)
       {
         killNote(note);
@@ -388,7 +388,7 @@ class Strumline extends FlxSpriteGroup
 
         var vwoosh:Bool = false;
 
-        if (PreferencesMenu.getPref('downscroll'))
+        if (Preferences.downscroll)
         {
           holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
         }
@@ -410,7 +410,7 @@ class Strumline extends FlxSpriteGroup
           holdNote.visible = false;
         }
 
-        if (PreferencesMenu.getPref('downscroll'))
+        if (Preferences.downscroll)
         {
           holdNote.y = this.y - holdNote.height + STRUMLINE_SIZE / 2;
         }
@@ -425,7 +425,7 @@ class Strumline extends FlxSpriteGroup
         holdNote.visible = true;
         var vwoosh:Bool = false;
 
-        if (PreferencesMenu.getPref('downscroll'))
+        if (Preferences.downscroll)
         {
           holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
         }
diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx
index 37bc674a5..f55799828 100644
--- a/source/funkin/play/notes/SustainTrail.hx
+++ b/source/funkin/play/notes/SustainTrail.hx
@@ -114,7 +114,7 @@ class SustainTrail extends FlxSprite
     height = sustainHeight(sustainLength, getScrollSpeed());
     // instead of scrollSpeed, PlayState.SONG.speed
 
-    flipY = PreferencesMenu.getPref('downscroll');
+    flipY = Preferences.downscroll;
 
     // alpha = 0.6;
     alpha = 1.0;
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index d1f9800ea..2666d2bff 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -63,10 +63,10 @@ abstract Save(RawSaveData)
             // Reasonable defaults.
             naughtyness: true,
             downscroll: false,
-            flashingMenu: true,
+            flashingLights: true,
             zoomCamera: true,
             debugDisplay: false,
-            pauseOnTabOut: true,
+            autoPause: true,
 
             controls:
               {
@@ -88,7 +88,7 @@ abstract Save(RawSaveData)
           {
             // No mods enabled.
             enabledMods: [],
-            modSettings: [],
+            modOptions: [],
           },
 
         optionsChartEditor:
@@ -98,6 +98,20 @@ abstract Save(RawSaveData)
       };
   }
 
+  public var options(get, never):SaveDataOptions;
+
+  function get_options():SaveDataOptions
+  {
+    return this.options;
+  }
+
+  public var modOptions(get, never):Map<String, Dynamic>;
+
+  function get_modOptions():Map<String, Dynamic>
+  {
+    return this.mods.modOptions;
+  }
+
   /**
    * The current session ID for the logged-in Newgrounds user, or null if the user is cringe.
    */
@@ -458,7 +472,7 @@ typedef SaveHighScoresData =
 typedef SaveDataMods =
 {
   var enabledMods:Array<String>;
-  var modSettings:Map<String, Dynamic>;
+  var modOptions:Map<String, Dynamic>;
 }
 
 /**
@@ -530,10 +544,10 @@ typedef SaveDataOptions =
   var downscroll:Bool;
 
   /**
-   * If disabled, the main menu won't flash when entering a submenu.
+   * If disabled, flashing lights in the main menu and other areas will be less intense.
    * @default `true`
    */
-  var flashingMenu:Bool;
+  var flashingLights:Bool;
 
   /**
    * If disabled, the camera bump synchronized to the beat.
@@ -551,7 +565,7 @@ typedef SaveDataOptions =
    * If enabled, the game will automatically pause when tabbing out.
    * @default `true`
    */
-  var pauseOnTabOut:Bool;
+  var autoPause:Bool;
 
   var controls:
     {
diff --git a/source/funkin/ui/PreferencesMenu.hx b/source/funkin/ui/PreferencesMenu.hx
index 4fa8f7f5b..812d0ab49 100644
--- a/source/funkin/ui/PreferencesMenu.hx
+++ b/source/funkin/ui/PreferencesMenu.hx
@@ -3,17 +3,16 @@ package funkin.ui;
 import flixel.FlxCamera;
 import flixel.FlxObject;
 import flixel.FlxSprite;
+import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
 import funkin.ui.AtlasText.AtlasFont;
 import funkin.ui.OptionsState.Page;
 import funkin.ui.TextMenuList.TextMenuItem;
 
 class PreferencesMenu extends Page
 {
-  public static var preferences:Map<String, Dynamic> = new Map();
-
   var items:TextMenuList;
+  var preferenceItems:FlxTypedSpriteGroup<FlxSprite>;
 
-  var checkboxes:Array<CheckboxThingie> = [];
   var menuCamera:FlxCamera;
   var camFollow:FlxObject;
 
@@ -27,13 +26,9 @@ class PreferencesMenu extends Page
     camera = menuCamera;
 
     add(items = new TextMenuList());
+    add(preferenceItems = new FlxTypedSpriteGroup<FlxSprite>());
 
-    createPrefItem('naughtyness', 'censor-naughty', true);
-    createPrefItem('downscroll', 'downscroll', false);
-    createPrefItem('flashing menu', 'flashing-menu', true);
-    createPrefItem('Camera Zooming on Beat', 'camera-zoom', true);
-    createPrefItem('FPS Counter', 'fps-counter', true);
-    createPrefItem('Auto Pause', 'auto-pause', false);
+    createPrefItems();
 
     camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70);
     if (items != null) camFollow.y = items.selectedItem.y;
@@ -48,128 +43,63 @@ class PreferencesMenu extends Page
     });
   }
 
-  public static function getPref(pref:String):Dynamic
+  /**
+   * Create the menu items for each of the preferences.
+   */
+  function createPrefItems():Void
   {
-    return preferences.get(pref);
+    createPrefItemCheckbox('Naughtyness', 'Toggle displaying raunchy content', function(value:Bool):Void {
+      Preferences.naughtyness = value;
+    }, Preferences.naughtyness);
+    createPrefItemCheckbox('Downscroll', 'Enable to make notes move downwards', function(value:Bool):Void {
+      Preferences.downscroll = value;
+    }, Preferences.downscroll);
+    createPrefItemCheckbox('Flashing Lights', 'Disable to dampen flashing effects', function(value:Bool):Void {
+      Preferences.flashingLights = value;
+    }, Preferences.flashingLights);
+    createPrefItemCheckbox('Camera Zooming on Beat', 'Disable to stop the camera bouncing to the song', function(value:Bool):Void {
+      Preferences.zoomCamera = value;
+    }, Preferences.zoomCamera);
+    createPrefItemCheckbox('Debug Display', 'Enable to show FPS and other debug stats', function(value:Bool):Void {
+      Preferences.debugDisplay = value;
+    }, Preferences.debugDisplay);
+    createPrefItemCheckbox('Auto Pause', 'Automatically pause the game when it loses focus', function(value:Bool):Void {
+      Preferences.autoPause = value;
+    }, Preferences.autoPause);
   }
 
-  // easy shorthand?
-  public static function setPref(pref:String, value:Dynamic):Void
+  function createPrefItemCheckbox(prefName:String, prefDesc:String, onChange:Bool->Void, defaultValue:Bool):Void
   {
-    preferences.set(pref, value);
-  }
+    var checkbox:CheckboxPreferenceItem = new CheckboxPreferenceItem(0, 120 * (items.length - 1 + 1), defaultValue);
 
-  public static function initPrefs():Void
-  {
-    preferenceCheck('censor-naughty', true);
-    preferenceCheck('downscroll', false);
-    preferenceCheck('flashing-menu', true);
-    preferenceCheck('camera-zoom', true);
-    preferenceCheck('fps-counter', true);
-    preferenceCheck('auto-pause', false);
-    preferenceCheck('master-volume', 1);
-
-    #if muted
-    setPref('master-volume', 0);
-    FlxG.sound.muted = true;
-    #end
-
-    if (!getPref('fps-counter')) FlxG.stage.removeChild(Main.fpsCounter);
-
-    FlxG.autoPause = getPref('auto-pause');
-  }
-
-  function createPrefItem(prefName:String, prefString:String, prefValue:Dynamic):Void
-  {
     items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
-      preferenceCheck(prefString, prefValue);
-
-      switch (Type.typeof(prefValue).getName())
-      {
-        case 'TBool':
-          prefToggle(prefString);
-
-        default:
-          trace('swag');
-      }
+      var value = !checkbox.currentValue;
+      onChange(value);
+      checkbox.currentValue = value;
     });
 
-    switch (Type.typeof(prefValue).getName())
-    {
-      case 'TBool':
-        createCheckbox(prefString);
-
-      default:
-        trace('swag');
-    }
-
-    trace(Type.typeof(prefValue).getName());
-  }
-
-  function createCheckbox(prefString:String)
-  {
-    var checkbox:CheckboxThingie = new CheckboxThingie(0, 120 * (items.length - 1), preferences.get(prefString));
-    checkboxes.push(checkbox);
-    add(checkbox);
-  }
-
-  /**
-   * Assumes that the preference has already been checked/set?
-   */
-  function prefToggle(prefName:String)
-  {
-    var daSwap:Bool = preferences.get(prefName);
-    daSwap = !daSwap;
-    preferences.set(prefName, daSwap);
-    checkboxes[items.selectedIndex].daValue = daSwap;
-    trace('toggled? ' + preferences.get(prefName));
-
-    switch (prefName)
-    {
-      case 'fps-counter':
-        if (getPref('fps-counter')) FlxG.stage.addChild(Main.fpsCounter);
-        else
-          FlxG.stage.removeChild(Main.fpsCounter);
-      case 'auto-pause':
-        FlxG.autoPause = getPref('auto-pause');
-    }
-
-    if (prefName == 'fps-counter') {}
+    preferenceItems.add(checkbox);
   }
 
   override function update(elapsed:Float)
   {
     super.update(elapsed);
 
-    // menuCamera.followLerp = CoolUtil.camLerpShit(0.05);
-
+    // Indent the selected item.
+    // TODO: Only do this on menu change?
     items.forEach(function(daItem:TextMenuItem) {
       if (items.selectedItem == daItem) daItem.x = 150;
       else
         daItem.x = 120;
     });
   }
-
-  static function preferenceCheck(prefString:String, defaultValue:Dynamic):Void
-  {
-    if (preferences.get(prefString) == null)
-    {
-      // Set the value to default.
-      preferences.set(prefString, defaultValue);
-      trace('Set preference to default: ${prefString} = ${defaultValue}');
-    }
-    else
-    {
-      trace('Found preference: ${prefString} = ${preferences.get(prefString)}');
-    }
-  }
 }
 
-class CheckboxThingie extends FlxSprite
+class CheckboxPreferenceItem extends FlxSprite
 {
-  public var daValue(default, set):Bool;
+  public var currentValue(default, set):Bool;
 
-  public function new(x:Float, y:Float, daValue:Bool = false)
+  public function new(x:Float, y:Float, defaultValue:Bool = false)
   {
     super(x, y);
 
@@ -180,7 +110,7 @@ class CheckboxThingie extends FlxSprite
     setGraphicSize(Std.int(width * 0.7));
     updateHitbox();
 
-    this.daValue = daValue;
+    this.currentValue = defaultValue;
   }
 
   override function update(elapsed:Float)
@@ -196,12 +126,17 @@ class CheckboxThingie extends FlxSprite
     }
   }
 
-  function set_daValue(value:Bool):Bool
+  function set_currentValue(value:Bool):Bool
   {
-    if (value) animation.play('checked', true);
+    if (value)
+    {
+      animation.play('checked', true);
+    }
     else
+    {
       animation.play('static');
+    }
 
-    return value;
+    return currentValue = value;
   }
 }
diff --git a/source/funkin/ui/TextMenuList.hx b/source/funkin/ui/TextMenuList.hx
index 0c9f9eb8b..521f46faf 100644
--- a/source/funkin/ui/TextMenuList.hx
+++ b/source/funkin/ui/TextMenuList.hx
@@ -10,7 +10,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
     super(navControls, wrapMode);
   }
 
-  public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback, fireInstantly = false)
+  public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void, fireInstantly = false)
   {
     var item = new TextMenuItem(x, y, name, font, callback);
     item.fireInstantly = fireInstantly;
@@ -20,7 +20,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
 
 class TextMenuItem extends TextTypedMenuItem<AtlasText>
 {
-  public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback)
+  public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
   {
     super(x, y, new AtlasText(0, 0, name, font), name, callback);
     setEmptyBackground();
@@ -29,7 +29,7 @@ class TextMenuItem extends TextTypedMenuItem<AtlasText>
 
 class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
 {
-  public function new(x = 0.0, y = 0.0, label:T, name:String, callback)
+  public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
   {
     super(x, y, label, name, callback);
   }
diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx
index b454ca429..c87f632de 100644
--- a/source/funkin/util/Constants.hx
+++ b/source/funkin/util/Constants.hx
@@ -3,6 +3,9 @@ package funkin.util;
 import flixel.util.FlxColor;
 import lime.app.Application;
 
+/**
+ * A store of unchanging, globally relevant values.
+ */
 class Constants
 {
   /**