How is the PinchSlider meant to be configured? - mrtk

I tried using the slider component for the first time and immediately some question came up:
How do I make the slider snap to the positions of the TickMarks?
If that's not possible, what are they for?
How do I change the amount of marks or remove all of them if I want to slider without stepping?
Why, when I change the Slider Track settings the visuals do not adjust?
I already see this going into a feature request ;)

These questions are probably best filed as issues on the MRTK github page. here's a link to file an issue.
But, answering your questions here for future visitors:
How do I make the slider snap to the positions of the TickMarks?
The default PinchSlider does not provide that functionality, but here is an example of how to do this by extending pinchslider (reference: issue 4140
public class SliderWithSnapPoints : PinchSlider
{
[SerializeField]
[Tooltip("The number of snap points")]
float snapPoints = 100;
float lastSnapPoint;
float snapPointSize;
public override void OnPointerDown(MixedRealityPointerEventData eventData)
{
base.OnPointerDown(eventData);
if (eventData.used)
{
lastSnapPoint = SliderValue;
snapPointSize = 1f / snapPoints;
}
}
/// <summary>
/// Handle slider value changes by dragging, and commit these changes.
/// </summary>
/// <remarks>
/// Note, this requires the MRTK pinch slider to implement this function signature,
/// and the pinch slider needs to call this function instead of instead setting SliderValue
/// directly.
/// </remarks>
protected override void OnPointerDragged(float newSliderValue)
{
var valueChange = Mathf.Abs(lastSnapPoint - newSliderValue);
if (valueChange >= snapPointSize)
{
lastSnapPoint = SliderValue = newSliderValue;
}
}
}
How do I change the amount of marks or remove all of them if I want to slider without stepping?
You can just delete or disable the tick marks in the scene hierarchy.
Why, when I change the Slider Track settings the visuals do not adjust?
The visuals do not adjust because they are not tied to the number of snap points, they are just provided for visual guidance.

I modified this implementation slightly:
using Microsoft.MixedReality.Toolkit.UI;
using UnityEngine;
public class SliderWithSnapPoints : PinchSlider
{
#pragma warning disable 649
[Tooltip("The number of snap points")]
[Min(2)]
[SerializeField] private int snapPoints = 5;
#pragma warning restore 649
/// <summary>
/// Handle slider value changes by dragging, and commit snap point aligned changes.
/// </summary>
/// <remarks>
/// Note, this requires the MRTK pinch slider to implement this function signature,
/// and the pinch slider needs to call this function instead of setting SliderValue
/// directly.
/// </remarks>
protected override void OnPointerDragged(float newSliderValue)
{
float gap = 1f / (snapPoints - 1f);
newSliderValue = 0.5f * gap + newSliderValue;
newSliderValue -= (newSliderValue % gap);
SliderValue = newSliderValue;
}
}
As the comment suggests you also have to edit the main PinchSlider script from:
public void OnPointerDragged(MixedRealityPointerEventData eventData)
{
if (eventData.Pointer == activePointer && !eventData.used)
{
var delta = activePointer.Position - startPointerPosition;
var handDelta = Vector3.Dot(SliderTrackDirection.normalized, delta);
SliderValue = Mathf.Clamp(startSliderValue + handDelta / SliderTrackDirection.magnitude, 0, 1);
// Mark the pointer data as used to prevent other behaviors from handling input events
eventData.Use();
}
}
To:
public void OnPointerDragged(MixedRealityPointerEventData eventData)
{
if (eventData.Pointer == activePointer && !eventData.used)
{
var delta = activePointer.Position - startPointerPosition;
var handDelta = Vector3.Dot(SliderTrackDirection.normalized, delta);
OnPointerDragged(Mathf.Clamp(startSliderValue + handDelta / SliderTrackDirection.magnitude, 0, 1));
// Mark the pointer data as used to prevent other behaviors from handling input events
eventData.Use();
}
}
protected virtual void OnPointerDragged(float newSliderValue)
{
SliderValue = newSliderValue;
}

Related

How do I modify the GrabPointer prefab to allow for more than the default 64 colliders in a scene?

I have a similar question to this, however for SpherePointer.
Using MRTK 2.2 as acquired by NuGet for Unity, I'm getting this warning pretty much every frame:
Maximum number of 64 colliders found in SpherePointer overlap query. Consider increasing the query buffer size in the pointer profile.
UnityEngine.Debug:LogWarning(Object)
Microsoft.MixedReality.Toolkit.Input.SpherePointerQueryInfo:TryUpdateQueryBufferForLayerMask(LayerMask, Vector3, QueryTriggerInteraction)
Microsoft.MixedReality.Toolkit.Input.SpherePointer:OnPreSceneQuery()
Microsoft.MixedReality.Toolkit.Input.FocusProvider:UpdatePointer(PointerData)
Microsoft.MixedReality.Toolkit.Input.FocusProvider:UpdatePointers()
Microsoft.MixedReality.Toolkit.Input.FocusProvider:Update()
Microsoft.MixedReality.Toolkit.<>c:<UpdateAllServices>b__63_0(IMixedRealityService)
Microsoft.MixedReality.Toolkit.MixedRealityToolkit:ExecuteOnAllServices(IEnumerable`1, Action`1)
Microsoft.MixedReality.Toolkit.MixedRealityToolkit:ExecuteOnAllServicesInOrder(Action`1)
Microsoft.MixedReality.Toolkit.MixedRealityToolkit:UpdateAllServices()
Microsoft.MixedReality.Toolkit.MixedRealityToolkit:Update()
I was able to successfully remove the similar warning for PokePointer using #Julia's response but I'm stumped as to how to do it for the GrabPointer prefab.
This prefab has a SpherePointer script attached but the SceneQueryBufferSize property isn't exposed in the inspector because SpherePointer's custom inspector (ShperePointerInspector.cs) doesn't expose it.
Great question! It looks like you've actually found a bug. Please use the following code to work around this for now, we will post a fix shortly. The issue to track this is here: issue 6878
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using Microsoft.MixedReality.Toolkit.Input.Editor;
using Microsoft.MixedReality.Toolkit.Input;
using UnityEditor;
namespace Microsoft.MixedReality.Toolkit.Input
{
[CustomEditor(typeof(SpherePointer))]
public class SpherePointerInspector : BaseControllerPointerInspector
{
private SerializedProperty sphereCastRadius;
private SerializedProperty nearObjectMargin;
private SerializedProperty grabLayerMasks;
private SerializedProperty triggerInteraction;
private SerializedProperty sceneQueryBufferSize;
private bool spherePointerFoldout = true;
protected override void OnEnable()
{
base.OnEnable();
sphereCastRadius = serializedObject.FindProperty("sphereCastRadius");
sceneQueryBufferSize = serializedObject.FindProperty("sceneQueryBufferSize");
nearObjectMargin = serializedObject.FindProperty("nearObjectMargin");
grabLayerMasks = serializedObject.FindProperty("grabLayerMasks");
triggerInteraction = serializedObject.FindProperty("triggerInteraction");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
spherePointerFoldout = EditorGUILayout.Foldout(spherePointerFoldout, "Sphere Pointer Settings", true);
if (spherePointerFoldout)
{
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(sphereCastRadius);
EditorGUILayout.PropertyField(sceneQueryBufferSize);
EditorGUILayout.PropertyField(nearObjectMargin);
EditorGUILayout.PropertyField(triggerInteraction);
EditorGUILayout.PropertyField(grabLayerMasks, true);
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
Thanks Julia,
Since I can't modify SpherePointerInspector.cs in my project as the MRTK is obtained from NuGet and has no actual .cs files to edit. Here's the solution I came up with as a temporary workaround until MRTK 2.3 is released.
I replaced SpherePointer.cs with MySpherePointer.cs on a copy of the GrabPointer.prefab.
MySpherePointer.cs
using Microsoft.MixedReality.Toolkit.Input;
/// <summary>
/// This class exists soley to expose SpherePointer.queryBufferPointerSize which is
/// masked by the default SpherePointerInspector.
/// </summary>
public class MySpherePointer : SpherePointer
{
}
MySpherePointerInspector.cs
using Microsoft.MixedReality.Toolkit.Input;
using UnityEditor;
/// <summary>
/// This class exists soley to expose SpherePointer.queryBufferPointerSize which is
/// masked by the default SpherePointerInspector.
/// </summary>
[CustomEditor(typeof(MySpherePointer))]
public class MySpherePointerInspector : SpherePointerInspector
{
private SerializedProperty sceneQueryBufferSize;
private bool hiddenSpherePointerFoldout = true;
protected override void OnEnable()
{
base.OnEnable();
sceneQueryBufferSize = serializedObject.FindProperty("sceneQueryBufferSize");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
hiddenSpherePointerFoldout = EditorGUILayout.Foldout(hiddenSpherePointerFoldout, "Hidden Sphere Pointer Settings", true);
if (hiddenSpherePointerFoldout)
{
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(sceneQueryBufferSize);
}
}
serializedObject.ApplyModifiedProperties();
}
}

How to remove white touch dot on Windows 10

I am creating a kiosk–like application for a Windows 10 PC with a touch screen as its only input interface. What I want to remove is the white touch dot that is displayed as a visual touch feedback (mostly together with a circle, which can be turned off).
Does anyone know how to do this?
I have already searched the registry if there was a cursor (*.cur) file which is used but did not find any results. Due to that I guess that the touch feedback is displayed differently.
Just to make sure — I do not want to lose touch functionality, only the visual feedback needs to be gone.
For WPF programs:
In order to remove the touch feedback from WPF elements, it's enough to set Stylus.IsTapFeedbackEnabled dependency property to false. You may want to do that within styles:
<Style x:Key="yourStyle">
<Setter Property="Stylus.IsTapFeedbackEnabled" Value="False" />
</Style>
The white touch dot you cited is actually a cursor. Whenever you touch the screen, Windows replaces the cursor set for the control with the touch cursor. To avoid this honestly ugly behavior, you can set your cursor to Cursors.None, so that the cursor will be hidden. Paste the code below inside your Window and you won't see your white touch dot anymore.
protected override void OnPreviewTouchDown(TouchEventArgs e)
{
base.OnPreviewTouchDown(e);
Cursor = Cursors.None;
}
protected override void OnPreviewTouchMove(TouchEventArgs e)
{
base.OnPreviewTouchMove(e);
Cursor = Cursors.None;
}
protected override void OnGotMouseCapture(MouseEventArgs e)
{
base.OnGotMouseCapture(e);
Cursor = Cursors.Arrow;
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
if (e.StylusDevice == null)
Cursor = Cursors.Arrow;
}
Your cursor will always be set to Cursors.Arrow when you're using the mouse, though, so you could lose a different cursor you had set to the window. It'd be an easy task to tweak this behavior — my code only serves for demonstration purposes.
For Windows Forms programs:
Looking at the Reference Source for Stylus.IsTapFeedbackEnabled, I've figured out the P/Invoke calls that happen under the hood. Here's what I've come up with for Windows Form applications:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace TouchableWinform
{
public class TouchableForm : Form
{
private const string TOUCH_SUPPORT_CATEGORY = "Touch support";
private const bool IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE = false;
private const bool IS_FLICKS_ENABLED_DEFAULT_VALUE = false;
private const bool IS_TAP_FEEDBACK_DEFAULT_VALUE = false;
private const bool IS_TOUCH_FEEDBACK_DEFAULT_VALUE = false;
/// <summary>
/// Gets or sets a values indicating whether press and hold is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets a values indicating whether press and hold is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE)]
public bool IsPressAndHoldEnabled
{
get;
set;
} = IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE;
/// <summary>
/// Gets or sets a value indicating whether flicks are enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets a value indicating whether flicks are enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_FLICKS_ENABLED_DEFAULT_VALUE)]
public bool IsFlicksEnabled
{
get;
set;
} = IS_FLICKS_ENABLED_DEFAULT_VALUE;
/// <summary>
/// Gets or sets whether a value indicating whether tap feedback is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets whether a value indicating whether tap feedback is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_TAP_FEEDBACK_DEFAULT_VALUE)]
public bool IsTapFeedbackEnabled
{
get;
set;
} = IS_TAP_FEEDBACK_DEFAULT_VALUE;
/// <summary>
/// Gets or sets whether a value indicating whether touch feedback is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets whether a value indicating whether touch feedback is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_TOUCH_FEEDBACK_DEFAULT_VALUE)]
public bool IsTouchFeedbackEnabled
{
get;
set;
} = IS_TOUCH_FEEDBACK_DEFAULT_VALUE;
/// <summary>
/// Processes Windows messages.
/// </summary>
/// <param name="m">The Windows <see cref="Message"/> to process.</param>
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x2CB:
m.Result = new IntPtr(1);
break;
case 0x2CC:
uint flags = 0;
if (!IsPressAndHoldEnabled)
flags |= 1;
if (!IsTapFeedbackEnabled)
flags |= 8;
flags |= IsTouchFeedbackEnabled ? (uint)0x100 : 0x200;
if (!IsFlicksEnabled)
flags |= 0x10000;
m.Result = new IntPtr(flags);
break;
default:
base.WndProc(ref m);
break;
}
}
}
}
Unfortunately, for some reason I couldn't still figure out, it doesn't quite look to be working. I'm open to any suggestions.
In Windows 10, you can use pointer messages to handle touches. This would make WPF act like native UWP app behavior. The white dot will disappear and touch move animation get fixed.
If you are using .NET Core, add this line to your App.xaml.cs constructor:
public partial class App : Application
{
public App()
{
AppContext.SetSwitch("Switch.System.Windows.Input.Stylus.EnablePointerSupport", true);
}
}
For .NET Framework, you should add App.config file to project. The contents are shown below:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.Input.Stylus.EnablePointerSupport=true" />
</runtime>
</configuration>
Sources:
How to enable pointer message support in WPF .NET Core
WPF will have a touch offset after trun on the WM_Pointer message

Windows App determine if TextBlock is trimmed

I have an GridItem that has a fixed Height/Width.
It contains a textblock that has the max line set.
How can I determine if this text is trimmed?
I want to add special functionality if it is trimmed.
Old Way - when TextWrapping is set to None
To know if a TextBlock is trimmed, we can subscribe to its SizeChanged event and compare its ActualWidth to the MaxWidth you specified. To get the right ActualWidth of the TextBlock, we will need to leave the TextTrimming to its default value (i.e. TextTrimming.None), and set it to trimmed once the width goes over.
New Way - when TextWrapping is set to Wrap
Now that I know because the TextWrapping is set to Wrap and assume the VirticalAlignment is not specified (default to Stretch), the Width will always stay the same. We only need to monitor the SizeChanged event when the actual height of the TextBlock exceeds the height of its parent.
Let's use a Behavior to encapsulate all the logic above. What needs to be mentioned here is that a static helper class with a bunch of attached properties or a new control that inherits from TextBlock can do exactly the same thing; but being a big Blend fan, I prefer to use Behaviors whenever possible.
The Behavior
public class TextBlockAutoTrimBehavior : DependencyObject, IBehavior
{
public bool IsTrimmed
{
get { return (bool)GetValue(IsTrimmedProperty); }
set { SetValue(IsTrimmedProperty, value); }
}
public static readonly DependencyProperty IsTrimmedProperty =
DependencyProperty.Register("IsTrimmed", typeof(bool), typeof(TextBlockAutoTrimBehavior), new PropertyMetadata(false));
public DependencyObject AssociatedObject { get; set; }
public void Attach(DependencyObject associatedObject)
{
this.AssociatedObject = associatedObject;
var textBlock = (TextBlock)this.AssociatedObject;
// subscribe to the SizeChanged event so we will know when the Width of the TextBlock goes over the MaxWidth
textBlock.SizeChanged += TextBlock_SizeChanged;
}
private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
// ignore the first time height change
if (e.PreviousSize.Height != 0)
{
var textBlock = (TextBlock)sender;
// notify the IsTrimmed dp so your viewmodel property will be notified via data binding
this.IsTrimmed = true;
// unsubscribe the event as we don't need it anymore
textBlock.SizeChanged -= TextBlock_SizeChanged;
// then we trim the TextBlock
textBlock.TextTrimming = TextTrimming.WordEllipsis;
}
}
public void Detach()
{
var textBlock = (TextBlock)this.AssociatedObject;
textBlock.SizeChanged += TextBlock_SizeChanged;
}
}
The XAML
<Grid HorizontalAlignment="Center" Height="73" VerticalAlignment="Center" Width="200" Background="#FFD2A6A6" Margin="628,329,538,366">
<TextBlock x:Name="MyTextBlock" TextWrapping="Wrap" Text="test" FontSize="29.333">
<Interactivity:Interaction.Behaviors>
<local:TextBlockAutoTrimBehavior IsTrimmed="{Binding IsTrimmedInVm}" />
</Interactivity:Interaction.Behaviors>
</TextBlock>
</Grid>
Note that the Behavior exposes a dependency property IsTrimmed, you can data bind it to a property in your viewmodel (i.e. IsTrimmedInVm in this case).
P.S. There's no FormattedText function in WinRT otherwise the implementation could be a little bit different.
We ended up making a static function
// Ensure block does not have MAXLINES or text trimming set prior to checking
public static bool IsTruncated(TextBlock block, int maxLines)
{
if (block == null)
{
throw new ArgumentNullException("block");
}
//the cut-off height is the height at which text will be cut off in the UI
var cutOffHeight = maxLines * block.LineHeight;
//determine whether the actual height of the TextBlock is greater than the cut-off height
return block.ActualHeight > cutOffHeight;
}
The trick is to make sure that Maxlines and text trimming is NOT set on the Textblock prior to running this function. After this function returns, that is when the Maxlines is set. In my case I just saved the returned Boolean in a containing object so I knew it was longer. Then I set maxlines and another button to see the extended content based on that Boolean.

UserControl doesn't show up

I'm working on a kind of Screen manager, here's the code:
/// <summary>
/// Change the screen according to type
/// </summary>
/// <author>Kay van Bree</author>
/// <param name="type">Replacement screen type</param>
public void ChangeScreen(ScreenType type)
{
// Swap old screen with loading screen
ReplaceScreen(screen, new LoadingScreen(this));
UserControl newScreen;
// Get instance of correct screen
switch(type)
{
case ScreenType.login:
newScreen = new LoginScreen(this);
break;
case ScreenType.dashboard:
newScreen = new DashboardScreen(this);
break;
default:
newScreen = new LoginScreen(this);
break;
}
// Swap loading screen with new screen
ReplaceScreen(screen, newScreen);
Text = "Attendance Tracker | " + screen;
}
private void ReplaceScreen(UserControl oldScreen, UserControl newScreen)
{
Controls.Remove(oldScreen);
screen = newScreen;
// Initialize screen
newScreen.BackColor = Color.Transparent;
newScreen.Location = new Point((Size.Width - screen.Size.Width) / 2, (Size.Height - screen.Size.Height) / 2);
Controls.Add(newScreen);
}
The goal of this function is to show a Loading Screen while it's initializing another UserControl (the screens are subclasses of UserControl). When the UserControl is initialized it removes the Loading screen and adds the UserControl to controls.
The problem is that my Loading screen (also a UserControl) won't show up. It won't execute any code before the other UserControl has been initialized.
I can't seem to find a solution, or what the problem is at all. Can you explain this behaviour? Am I approaching the loading screen wrong? Does C# load the constructor in a different thread or something? What's the problem?
[edit] By the way. The loading screen is just something I wanted to add in. The rest of the screen manager works just fine, so if I'm approaching it wrong, I'll probably drop the whole loading thing.
A simple Refresh() solved my problem.

XNA 4.0 Graphics Device Error

I know that the way the declaration of the graphics device is suppose to look in the main Game1() constructor is:
GraphicsDeviceManager graphics;
graphics = new GraphicsDeviceManager(this);
then you can use stuff like:
graphics.PreferredBackBufferWidth = 1366;
But if I declare the same inside a separate class, what do I fill in for "this"?
GraphicsDeviceManager graphics;
graphics = new GraphicsDeviceManager(?);
EDIT:
After modifying everything as you said, I now get a error that sends me to this line of code:
/// <summary>
/// Event handler for when the Play Game menu entry is selected.
/// </summary>
void PlayGameMenuEntrySelected(object sender, PlayerIndexEventArgs e, Game game)
{
LoadingScreen.Load(ScreenManager, true, e.PlayerIndex,
new GameplayScreen(game));
}
This program is the menu sample you can get from Microsoft, heavily modified of course, this is the code that executes when you hit enter on the main menu screen with "Play Game" highlighted. I guess the problem is passing the variable.
Edit 2:
I fixed the code I think but now it sent me to this line and I don't know how to edit it.
playGameMenuEntry.Selected += PlayGameMenuEntrySelected;
You need a reference to your Game instance. I'd just pass it to your new class constructor.
For example, if you want to move the GraphicsDeviceManager instance into a separate class, Foo,
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
private Foo _foo;
public Game1()
{
_foo = new Foo(this);
}
protected override void Dispose(bool disposing)
{
if (_foo != null)
{
_foo.Dispose();
_foo = null;
}
base.Dispose(disposing);
}
}
public class Foo : IDisposable
{
private Game _game;
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
public Foo(Game game)
{
_game = game;
_game.Content.RootDirectory = "Content";
_graphics = new GraphicsDeviceManager(_game);
_spriteBatch = new SpriteBatch(_game.GraphicsDevice);
}
public void Dispose()
{
if (!_spriteBatch.IsDisposed)
{
_spriteBatch.Dispose();
_spriteBatch = null;
}
}
}
Update
It's important to properly dispose of a lot of XNA objects (otherwise, you get memory leaks). See Foo.Dispose() and the Game1.Dispose() override above.

Resources