using Nova; using System; using UnityEngine; using UnityEngine.Scripting.APIUpdating; namespace NovaSamples.UIControls { /// /// The type used to display a control and its list of selectable options. /// [Serializable] [MovedFrom(false, null, "Assembly-CSharp")] public class DropdownVisuals : UIControlVisuals { [Header("Collapsed Visuals")] [Tooltip("The TextBlock to display the label of currently selected option.")] public TextBlock SelectionLabel = null; [Tooltip("The background visual element to change as the dropdown is pressed and released.")] public UIBlock2D Background = null; [Tooltip("The visual to rotate as the dropdown is expanded or collapsed.")] public UIBlock2D DropdownArrow = null; [Header("Expanded Visuals")] [Tooltip("The visual root of the content to display when the dropdown is expanded.")] public UIBlock ExpandedViewRoot = null; [Tooltip("The ListView used to display the different options in the dropdown.")] public ListView OptionsView = null; [Tooltip("If false, the dropdown will expand upwards.")] public bool ExpandDown = true; [Header("Options View Row Colors")] [Tooltip("The default background color of the dropdown list items. Every even-row item will be this color.")] public Color DefaultRowColor; [Tooltip("The alternative background color of the dropdown list items. Every odd-row item will be this color.")] public Color AlternatingRowColor; protected override UIBlock TransitionTargetFallback => Background; /// /// Is the dropdown list open? /// public bool IsExpanded => ExpandedViewRoot.gameObject.activeSelf; [NonSerialized, HideInInspector] private bool eventHandlersRegistered = false; public event Action OnValueChanged = null; /// /// The datasource used to populate this dropdown control and its list of options. /// public DropdownData DataSource { get; private set; } /// /// Expand this dropdown control and populate its expanded /// with the options in the datasource. /// /// The datasource to populate this dropdown's list of options. public void Expand(DropdownData dataSource) { // Ensure the dropdown expands in the desired direction ExpandedViewRoot.Alignment.Y = ExpandDown ? VerticalAlignment.Top : VerticalAlignment.Bottom; // Cache the current datasource, so we can update it in // the event a new option is selected from the dropdown. DataSource = dataSource; // Verify the bind/interaction handlers // are registered for when we populate // the options view EnsureEventHandlersRegistered(); // Enable the OptionsView and other visuals ExpandedViewRoot.gameObject.SetActive(true); // Set the datasource if it has changed if (OptionsView.GetDataSource() != dataSource.Options) { OptionsView.SetDataSource(dataSource.Options); } // Update the dropdown arrow rotation DropdownArrow.transform.localRotation = Quaternion.Euler(0, 0, 90); } /// /// Collapse the dropdown list of options. /// public void Collapse() { // Disable the list of selectable options ExpandedViewRoot.gameObject.SetActive(false); // Update the dropdown arrow rotation DropdownArrow.transform.localRotation = Quaternion.identity; } /// /// Register the desired databind/interaction event handlers if not already subscribed. /// private void EnsureEventHandlersRegistered() { if (eventHandlersRegistered) { return; } // Register data binder OptionsView.AddDataBinder(BindOption); // Register gesture handlers OptionsView.AddGestureHandler(HandleOptionSelected); OptionsView.AddGestureHandler(ToggleVisuals.HandleHovered); OptionsView.AddGestureHandler(ToggleVisuals.HandleUnhovered); OptionsView.AddGestureHandler(ToggleVisuals.HandlePressed); OptionsView.AddGestureHandler(ToggleVisuals.HandleReleased); OptionsView.AddGestureHandler(ToggleVisuals.HandlePressCanceled); eventHandlersRegistered = true; } /// /// Event handler to react to a list item in the dropdown (bound to one of the options in ) being selected. /// /// The click event. /// The object that was selected. /// The index into of the object represented by . public void HandleOptionSelected(Gesture.OnClick evt, ToggleVisuals option, int index) { // If a new option was selected, update the datasource. if (index != DataSource.SelectedIndex) { // Get the currently selected list item if it's in view. if (OptionsView.TryGetItemView(DataSource.SelectedIndex, out ItemView selectedListItem)) { // Get the list item's visuals as a ToggleVisuals. ToggleVisuals selectedVisuals = selectedListItem.Visuals as ToggleVisuals; // Disable the IsOnIndicator on the list item to indicate it's no longer selected. selectedVisuals.IsOnIndicator.gameObject.SetActive(false); } // Set the selected index in the data source. DataSource.SelectedIndex = index; // Update this controls SelectionLabel to reflect the newly selected option. SelectionLabel.Text = DataSource.CurrentSelection; // Enable the IsOnon the list item to indicate its selected state. option.IsOnIndicator.gameObject.SetActive(true); OnValueChanged?.Invoke(DataSource.CurrentSelection); } // Collapse the expanded OptionsView Collapse(); } /// /// Sets the selection label to the provided value /// public void InitSelectionLabel(string label) { SelectionLabel.Text = label; } /// /// Bind the visual to its corresponding data object. /// /// The bind event data. /// The object representing the data being bound into view. /// The index into of the data object being bound into view. private void BindOption(Data.OnBind evt, ToggleVisuals option, int index) { // The UserData on this bind event is the same value stored // at the given `index` into the list of options. // // I.e. // evt.UserData == DataSource.Options[index] option.Label.Text = evt.UserData; // Highlight the selected row to differentiate it from the rest option.IsOnIndicator.gameObject.SetActive(index == DataSource.SelectedIndex); // For aesthetics and legibility we want to alternate the background color of rows // in the options view. Even numbered rows will use the default color, and odd numbered // rows will use the alternating color. Color defaultColor = index % 2 == 0 ? DefaultRowColor : AlternatingRowColor; option.Background.Color = defaultColor; option.DefaultColor = defaultColor; option.PressedColor = PressedColor; option.HoveredColor = HoveredColor; } /// /// A utility method to restore the visual state of /// object when it's unhovered. /// /// The release event. /// The receiving the release event. public static void HandleUnhovered(Gesture.OnUnhover evt, DropdownVisuals visuals) { if (evt.Receiver.transform.IsChildOf(visuals.ExpandedViewRoot.transform)) { // The hierarchy is hovered, which will happen if one of the objects in visuals.OptionsView // is hovered, but we don't want to highlight the background // element in that context. return; } ButtonVisuals.HandleUnhovered(evt, visuals); } /// /// A utility method to indicate a hovered visual state of object. /// /// The press event. /// The receiving the press event. public static void HandleHovered(Gesture.OnHover evt, DropdownVisuals visuals) { if (evt.Receiver.transform.IsChildOf(visuals.ExpandedViewRoot.transform)) { // The hierarchy is hovered, which will happen if one of the objects in visuals.OptionsView // is hovered, but we don't want to highlight the background // element in that context. return; } ButtonVisuals.HandleHovered(evt, visuals); } /// /// A utility method to restore the visual state of /// object when its active gesture (likely a press) is canceled. /// /// The cancel event. /// The receiving the cancel event. public static void HandlePressCanceled(Gesture.OnCancel evt, DropdownVisuals visuals) { if (evt.Receiver.transform.IsChildOf(visuals.ExpandedViewRoot.transform)) { // The hierarchy is hovered, which will happen if one of the objects in visuals.OptionsView // is hovered, but we don't want to highlight the background // element in that context. return; } ButtonVisuals.HandlePressCanceled(evt, visuals); } /// /// A utility method to restore the visual state of /// object when its active gesture (likely a press) is released. /// /// The release event. /// The receiving the release event. public static void HandleReleased(Gesture.OnRelease evt, DropdownVisuals visuals) { if (evt.Receiver.transform.IsChildOf(visuals.ExpandedViewRoot.transform)) { // The hierarchy is released, which will happen if one of the objects in visuals.OptionsView // is released, but we don't want to highlight the background // element in that context. return; } ButtonVisuals.HandleReleased(evt, visuals); } /// /// A utility method to indicate a pressed visual state of object. /// /// The press event. /// The receiving the press event. public static void HandlePressed(Gesture.OnPress evt, DropdownVisuals visuals) { if (evt.Receiver.transform.IsChildOf(visuals.ExpandedViewRoot.transform)) { // The hierarchy is pressed, which will happen if one of the objects in visuals.OptionsView // is pressed, but we don't want to highlight the background element in that context. return; } ButtonVisuals.HandlePressed(evt, visuals); } } }