From f8bc5f726707d434e4dc9244d91fa5db253555f8 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 27 Feb 2021 17:51:58 +1300 Subject: [PATCH] Minor Options Revamp (#86) * change the way options are displayed * make Options.Field abstract, make relevant options generics * add option reprs --- Mods/ModMenu/OptionManager.py | 22 +++++-- Mods/ModMenu/Options.py | 112 +++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 40 deletions(-) diff --git a/Mods/ModMenu/OptionManager.py b/Mods/ModMenu/OptionManager.py index 50eea8eb..7997396c 100644 --- a/Mods/ModMenu/OptionManager.py +++ b/Mods/ModMenu/OptionManager.py @@ -14,6 +14,15 @@ _MOD_OPTIONS_EVENT_ID: int = 1417 _MOD_OPTIONS_MENU_NAME: str = "MODS" +_INDENT: int = 2 + + +class _ModHeader(Options.Field): + def __init__(self, Caption: str) -> None: + self.Caption = Caption + self.Description = "" + self.IsHidden = False + def _create_data_provider(name: str) -> unrealsdk.UObject: """ @@ -149,15 +158,18 @@ def _DataProviderOptionsBasePopulate(caller: unrealsdk.UObject, function: unreal continue if not one_shown: one_shown = True - all_options.append(Options.Field(mod.Name)) + all_options.append(_ModHeader(mod.Name)) all_options.append(option) _nested_options_stack.append(Options.Nested(_MOD_OPTIONS_MENU_NAME, "", all_options)) + first_level = len(_nested_options_stack) == 1 for idx, option in enumerate(_nested_options_stack[-1].Children): if option.IsHidden: continue + indent = " " * _INDENT if first_level and not isinstance(option, _ModHeader) else "" + if isinstance(option, Options.Spinner): spinner_idx: int if isinstance(option, Options.Boolean): @@ -166,12 +178,12 @@ def _DataProviderOptionsBasePopulate(caller: unrealsdk.UObject, function: unreal spinner_idx = option.Choices.index(option.CurrentValue) params.TheList.AddSpinnerListItem( - idx, option.Caption, False, spinner_idx, option.Choices + idx, indent + option.Caption, False, spinner_idx, option.Choices ) elif isinstance(option, Options.Slider): params.TheList.AddSliderListItem( idx, - option.Caption, + indent + option.Caption, False, option.CurrentValue, option.MinValue, @@ -180,11 +192,9 @@ def _DataProviderOptionsBasePopulate(caller: unrealsdk.UObject, function: unreal ) elif isinstance(option, Options.Field): disabled = False - new = False if isinstance(option, Options.Nested): disabled = not _is_anything_shown(option.Children) - new = True - params.TheList.AddListItem(idx, option.Caption, disabled, new) + params.TheList.AddListItem(idx, indent + option.Caption, disabled, False) caller.AddDescription(idx, option.Description) diff --git a/Mods/ModMenu/Options.py b/Mods/ModMenu/Options.py index 85482d05..9b4a4f31 100644 --- a/Mods/ModMenu/Options.py +++ b/Mods/ModMenu/Options.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Any, Optional, Sequence, Tuple +from reprlib import recursive_repr +from typing import Any, Generic, Optional, Sequence, Tuple, TypeVar from . import DeprecationHelper as dh @@ -14,6 +15,8 @@ "Value", ) +T = TypeVar("T") + class Base(ABC): """ @@ -39,7 +42,7 @@ def __init__( raise NotImplementedError -class Value(Base): +class Value(Base, Generic[T]): """ The abstract base class for all options that store a value. @@ -52,22 +55,22 @@ class Value(Base): IsHidden: If the option is hidden from the options menu. """ - CurrentValue: Any - StartingValue: Any + CurrentValue: T + StartingValue: T @abstractmethod def __init__( self, Caption: str, Description: str, - StartingValue: Any, + StartingValue: T, *, IsHidden: bool = True ) -> None: raise NotImplementedError -class Hidden(Value): +class Hidden(Value[T]): """ A hidden option that never displays in the menu but stores an arbitrary (json serializable) value to the settings file. @@ -81,11 +84,12 @@ class Hidden(Value): StartingValue: The default value of the option. IsHidden: If the option is hidden from the options menu. This is forced to True. """ + def __init__( self, Caption: str, Description: str = "", - StartingValue: Any = None, + StartingValue: T = None, # type: ignore *, IsHidden: bool = True ) -> None: @@ -116,8 +120,18 @@ def IsHidden(self) -> bool: # type: ignore def IsHidden(self, val: bool) -> None: pass + @recursive_repr() + def __repr__(self) -> str: + return ( + f"Hidden(" + f"Caption={repr(self.Caption)}," + f"Description={repr(self.Description)}," + f"*,IsHidden={repr(self.IsHidden)}" + f")" + ) -class Slider(Value): + +class Slider(Value[int]): """ An option which allows users to select a value along a slider. @@ -176,8 +190,23 @@ def __init__( self.Increment = Increment self.IsHidden = IsHidden - -class Spinner(Value): + @recursive_repr() + def __repr__(self) -> str: + return ( + f"Slider(" + f"Caption={repr(self.Caption)}," + f"Description={repr(self.Description)}," + f"CurrentValue={repr(self.CurrentValue)}," + f"StartingValue={repr(self.StartingValue)}," + f"MinValue={repr(self.MinValue)}," + f"MaxValue={repr(self.MaxValue)}," + f"Increment={repr(self.Increment)}," + f"*,IsHidden={repr(self.IsHidden)}" + f")" + ) + + +class Spinner(Value[str]): """ An option which allows users to select one value from a sequence of strings. @@ -242,8 +271,21 @@ def __init__( f"Provided starting value '{self.StartingValue}' is not in the list of choices." ) - -class Boolean(Spinner): + @recursive_repr() + def __repr__(self) -> str: + return ( + f"Spinner(" + f"Caption={repr(self.Caption)}," + f"Description={repr(self.Description)}," + f"CurrentValue={repr(self.CurrentValue)}," + f"StartingValue={repr(self.StartingValue)}," + f"Choices={repr(self.Choices)}," + f"*,IsHidden={repr(self.IsHidden)}" + f")" + ) + + +class Boolean(Spinner, Value[bool]): """ A special form of a spinner, with two options representing boolean values. @@ -309,6 +351,19 @@ def CurrentValue(self, val: Any) -> None: else: self._current_value = bool(val) + @recursive_repr() + def __repr__(self) -> str: + return ( + f"Boolean(" + f"Caption={repr(self.Caption)}," + f"Description={repr(self.Description)}," + f"CurrentValue={repr(self.CurrentValue)}," + f"StartingValue={repr(self.StartingValue)}," + f"Choices={repr(self.Choices)}," + f"*,IsHidden={repr(self.IsHidden)}" + f")" + ) + class Field(Base): """ @@ -320,33 +375,11 @@ class Field(Base): IsHidden: If the field is hidden from the options menu. """ - def __init__( - self, - Caption: str, - Description: str = "", - *, - IsHidden: bool = False - ) -> None: - """ - Creates the option. - - Args: - Caption: The name of the option. - Description: A short description of the option to show when hovering over it in the menu. - IsHidden (keyword only): If the value is hidden from the options menu. - """ - self.Caption = Caption - self.Description = Description - self.IsHidden = IsHidden - class Nested(Field): """ A field which when clicked opens up a nested menu with more options. - These are distinguished from normal fields by having the "new" exclaimation mark to the side of - it, but you should probably still give it a meaningful description. - Note that these fields will be disabled if all child options are either hidden or other disabled nested fields. @@ -383,3 +416,14 @@ def __init__( self.Description = Description self.Children = Children self.IsHidden = IsHidden + + @recursive_repr() + def __repr__(self) -> str: + return ( + f"Nested(" + f"Caption={repr(self.Caption)}," + f"Description={repr(self.Description)}," + f"Children={repr(self.Children)}," + f"*,IsHidden={repr(self.IsHidden)}" + f")" + )