private InputControl InstantiateLayout(InputControlLayout layout, InternedString variants, InternedString name,
                                               InputControl parent)
        {
            Debug.Assert(layout.type != null, "Layout has no type set on it");

            // No, so create a new control.
            var controlObject = Activator.CreateInstance(layout.type);

            if (!(controlObject is InputControl control))
            {
                throw new InvalidOperationException(
                          $"Type '{layout.type.Name}' referenced by layout '{layout.name}' is not an InputControl");
            }

            // If it's a device, perform some extra work specific to the control
            // hierarchy root.
            if (control is InputDevice controlAsDevice)
            {
                if (parent != null)
                {
                    throw new InvalidOperationException(
                              $"Cannot instantiate device layout '{layout.name}' as child of '{parent.path}'; devices must be added at root");
                }

                m_Device = controlAsDevice;
                m_Device.m_StateBlock.byteOffset = 0;
                m_Device.m_StateBlock.bitOffset  = 0;
                m_Device.m_StateBlock.format     = layout.stateFormat;

                // If we have an existing device, we'll start the various control arrays
                // from scratch. Note that all the controls still refer to the existing
                // arrays and so we can iterate children, for example, just fine while
                // we are rebuilding the control hierarchy.
                m_Device.m_AliasesForEachControl  = null;
                m_Device.m_ChildrenForEachControl = null;
                m_Device.m_UsagesForEachControl   = null;
                m_Device.m_UsageToControl         = null;

                if (layout.m_UpdateBeforeRender == true)
                {
                    m_Device.m_DeviceFlags |= InputDevice.DeviceFlags.UpdateBeforeRender;
                }
            }
            else if (parent == null)
            {
                // Someone did "new InputDeviceBuilder(...)" with a control layout.
                // We don't support creating control hierarchies without a device at the root.
                throw new InvalidOperationException(
                          $"Toplevel layout used with InputDeviceBuilder must be a device layout; '{layout.name}' is a control layout");
            }

            // Name defaults to name of layout.
            if (name.IsEmpty())
            {
                name = layout.name;

                // If there's a namespace in the layout name, snip it out.
                var indexOfLastColon = name.ToString().LastIndexOf(':');
                if (indexOfLastColon != -1)
                {
                    name = new InternedString(name.ToString().Substring(indexOfLastColon + 1));
                }
            }

            // Variant defaults to variants of layout.
            if (variants.IsEmpty())
            {
                variants = layout.variants;

                if (variants.IsEmpty())
                {
                    variants = InputControlLayout.DefaultVariant;
                }
            }

            control.m_Name = name;
            control.m_DisplayNameFromLayout = layout.m_DisplayName; // No short display names at layout roots.
            control.m_Layout   = layout.name;
            control.m_Variants = variants;
            control.m_Parent   = parent;
            control.m_Device   = m_Device;

            // Create children and configure their settings from our
            // layout values.
            var haveChildrenUsingStateFromOtherControl = false;

            try
            {
                // Pass list of existing control on to function as we may have decided to not
                // actually reuse the existing control (and thus control.m_ChildrenReadOnly will
                // now be blank) but still want crawling down the hierarchy to preserve existing
                // controls where possible.
                AddChildControls(layout, variants, control,
                                 ref haveChildrenUsingStateFromOtherControl);
            }
            catch
            {
                ////TODO: remove control from collection and rethrow
                throw;
            }

            // Come up with a layout for our state.
            ComputeStateLayout(control);

            // Finally, if we have child controls that take their state blocks from other
            // controls, assign them their blocks now.
            if (haveChildrenUsingStateFromOtherControl)
            {
                var controls = layout.m_Controls;
                for (var i = 0; i < controls.Length; ++i)
                {
                    ref var item = ref controls[i];
                    if (string.IsNullOrEmpty(item.useStateFrom))
                    {
                        continue;
                    }
                    ApplyUseStateFrom(control, ref item, layout);
                }
            }
Example #2
0
        private InputControl InstantiateLayout(InputControlLayout layout, InternedString variant, InternedString name, InputControl parent, InputControl existingControl)
        {
            InputControl control;

            // If we have an existing control, see whether it's usable.
            if (existingControl != null && existingControl.layout == layout.name && existingControl.GetType() == layout.type)
            {
                control = existingControl;

                ////FIXME: the re-use path probably has some data that could stick around when it shouldn't
                control.m_UsagesReadOnly = new ReadOnlyArray <InternedString>();
                control.ClearProcessors();
            }
            else
            {
                Debug.Assert(layout.type != null);

                // No, so create a new control.
                var controlObject = Activator.CreateInstance(layout.type);
                control = controlObject as InputControl;
                if (control == null)
                {
                    throw new Exception(string.Format("Type '{0}' referenced by layout '{1}' is not an InputControl",
                                                      layout.type.Name, layout.name));
                }
            }

            // If it's a device, perform some extra work specific to the control
            // hierarchy root.
            var controlAsDevice = control as InputDevice;

            if (controlAsDevice != null)
            {
                if (parent != null)
                {
                    throw new Exception(string.Format(
                                            "Cannot instantiate device layout '{0}' as child of '{1}'; devices must be added at root",
                                            layout.name, parent.path));
                }

                m_Device = controlAsDevice;
                m_Device.m_StateBlock.byteOffset = 0;
                m_Device.m_StateBlock.format     = layout.stateFormat;

                // If we have an existing device, we'll start the various control arrays
                // from scratch. Note that all the controls still refer to the existing
                // arrays and so we can iterate children, for example, just fine while
                // we are rebuilding the control hierarchy.
                m_Device.m_AliasesForEachControl  = null;
                m_Device.m_ChildrenForEachControl = null;
                m_Device.m_UsagesForEachControl   = null;
                m_Device.m_UsageToControl         = null;

                // But we preserve IDs and descriptions of existing devices.
                if (existingControl != null)
                {
                    var existingDevice = (InputDevice)existingControl;
                    m_Device.m_Id          = existingDevice.m_Id;
                    m_Device.m_Description = existingDevice.m_Description;
                }

                if (layout.m_UpdateBeforeRender == true)
                {
                    m_Device.m_Flags |= InputDevice.Flags.UpdateBeforeRender;
                }
            }
            else if (parent == null)
            {
                // Someone did "new InputDeviceBuilder(...)" with a control layout.
                // We don't support creating control hierarchies without a device at the root.
                throw new InvalidOperationException(
                          string.Format(
                              "Toplevel layout used with InputDeviceBuilder must be a device layout; '{0}' is a control layout",
                              layout.name));
            }

            // Name defaults to name of layout.
            if (name.IsEmpty())
            {
                name = layout.name;

                // If there's a namespace in the layout name, snip it out.
                var indexOfLastColon = name.ToString().LastIndexOf(':');
                if (indexOfLastColon != -1)
                {
                    name = new InternedString(name.ToString().Substring(indexOfLastColon + 1));
                }
            }

            // Variant defaults to variant of layout.
            if (variant.IsEmpty())
            {
                variant = layout.variant;

                if (variant.IsEmpty())
                {
                    variant = InputControlLayout.DefaultVariant;
                }
            }

            control.m_Name = name;
            control.m_DisplayNameFromLayout = layout.m_DisplayName;
            control.m_Layout  = layout.name;
            control.m_Variant = variant;
            control.m_Parent  = parent;
            control.m_Device  = m_Device;

            // Create children and configure their settings from our
            // layout values.
            var haveChildrenUsingStateFromOtherControl = false;

            try
            {
                // Pass list of existing control on to function as we may have decided to not
                // actually reuse the existing control (and thus control.m_ChildrenReadOnly will
                // now be blank) but still want crawling down the hierarchy to preserve existing
                // controls where possible.
                AddChildControls(layout, variant, control,
                                 existingControl != null ? existingControl.m_ChildrenReadOnly : (ReadOnlyArray <InputControl>?)null,
                                 ref haveChildrenUsingStateFromOtherControl);
            }
            catch
            {
                ////TODO: remove control from collection and rethrow
                throw;
            }

            // Come up with a layout for our state.
            ComputeStateLayout(control);

            // Finally, if we have child controls that take their state blocks from other
            // controls, assign them their blocks now.
            if (haveChildrenUsingStateFromOtherControl)
            {
                foreach (var controlLayout in layout.controls)
                {
                    if (string.IsNullOrEmpty(controlLayout.useStateFrom))
                    {
                        continue;
                    }

                    var child = TryGetControl(control, controlLayout.name);
                    Debug.Assert(child != null);

                    // Find the referenced control.
                    var referencedControl = TryGetControl(control, controlLayout.useStateFrom);
                    if (referencedControl == null)
                    {
                        throw new Exception(
                                  string.Format(
                                      "Cannot find control '{0}' referenced in 'useStateFrom' of control '{1}' in layout '{2}'",
                                      controlLayout.useStateFrom, controlLayout.name, layout.name));
                    }

                    // Copy its state settings.
                    child.m_StateBlock = referencedControl.m_StateBlock;

                    // At this point, all byteOffsets are relative to parents so we need to
                    // walk up the referenced control's parent chain and add offsets until
                    // we are at the same level that we are at.
                    for (var parentInChain = referencedControl.parent; parentInChain != control; parentInChain = parentInChain.parent)
                    {
                        child.m_StateBlock.byteOffset += parentInChain.m_StateBlock.byteOffset;
                    }
                }
            }

            return(control);
        }