TipToe 0.1.4
See the version list below for details.
dotnet add package TipToe --version 0.1.4
NuGet\Install-Package TipToe -Version 0.1.4
<PackageReference Include="TipToe" Version="0.1.4" />
<PackageVersion Include="TipToe" Version="0.1.4" />
<PackageReference Include="TipToe" />
paket add TipToe --version 0.1.4
#r "nuget: TipToe, 0.1.4"
#:package TipToe@0.1.4
#addin nuget:?package=TipToe&version=0.1.4
#tool nuget:?package=TipToe&version=0.1.4
TipToe
A quiet, careful and deliberate WinUI TeachingTip orchestrator.
Declare your teaching tips once. TipToe decides when — and whether — each one is worth showing.
using TipToe;
var guide = Guide.Attach(this, RootGrid);
guide.Add(new Tip
{
Id = "command-palette",
Title = "Jump anywhere",
Subtitle = "Press Ctrl+K to search every command.",
Target = () => SearchBox,
When = () => DocumentCount >= 3,
});
…and, in the feature itself:
guide.Exercise("command-palette");
That is the whole contract. TipToe will not show that tip if the user has already opened the command palette, already dismissed it, is mid-drag, is looking at another app, saw a different tip four seconds ago, saw this tip earlier today, or the anchor is not currently on screen.
The principle throughout: a teaching tip at the wrong moment is worse than no teaching tip at all. Almost everything the library does is a reason not to show something.
Install
dotnet add package TipToe
net8.0-windows10.0.19041.0. Works in packaged and unpackaged apps. One AnyCPU assembly, no
resource index, no per-architecture assets.
When a tip appears
A tip is shown only when every one of these is true, checked in this order:
| Gate | |
|---|---|
| 1 | Tips are enabled |
| 2 | The user has not exercised the feature |
| 3 | The user has not dismissed the tip |
| 4 | It is under its show budget (MaxShows, default 3) |
| 5 | Its prerequisites have been exercised |
| 6 | Its When condition is true |
| 7 | It has not been shown within RepeatDelay (default 1 day) |
| 8 | No other tip is showing |
| 9 | CooldownBetweenTips has elapsed (default 4 s) |
| 10 | The session cap is not reached (default: none) |
| 11 | StartupGrace has elapsed (default 4 s) |
| 12 | The window is active |
| 13 | No suppressor is active |
| 14 | The user has been idle for UserIdleFor (default 6 s) |
| 15 | The anchor resolves and is on screen |
Inspect() reports which of these each tip is currently sitting on, computed through the same code
path the runtime uses — so what it says and what happens cannot disagree.
Telling TipToe about your app
Suppressors — "not now", named so a blocked tip can explain itself:
guide.AddSuppressor("drag-in-progress", () => _dragging);
guide.AddSuppressor("playback", () => Player.IsPlaying);
A tip can opt out of one with Ignores = ["playback"].
Activity from elsewhere — a single window is not the whole app:
secondWindow.PointerPressed += (_, _) => guide.ReportActivity();
Exercised — call from the feature, never from near the tip, so a user who discovered it on their own is never taught it afterwards. Safe to call every time; only the first has any effect.
Options
Every value has a defensible default; nothing is required.
var guide = Guide.Attach(window, host, new TipToeOptions
{
UserIdleFor = TimeSpan.FromSeconds(6),
StartupGrace = TimeSpan.FromSeconds(4),
CooldownBetweenTips = TimeSpan.FromSeconds(4),
RepeatDelay = TimeSpan.FromDays(1),
MaxTipsPerSession = 0, // unlimited
DefaultMaxShows = 3,
StateRevision = 1, // bump to discard everything persisted
Log = m => Debug.WriteLine(m),
Presenter = new TeachingTipPresenter { StyleTip = t => t.Background = MyBrush },
});
Validate() runs at attach and throws on a configuration that could never work — a guide that runs
perfectly and shows nothing is a bad way to find that out.
Bring your own control
TipToe owns whether and when; it does not own what it looks like. Implement ITipPresenter to
use a custom coach-mark, callout or inline banner, globally or for a single tip:
public sealed class MyCoachMark : ITipPresenter
{
public void Initialize(Panel host) { ... }
public bool IsShowing => ...;
public void Show(TipPresentation p) { ... }
public void Hide() { ... }
public event EventHandler<TipClosedEventArgs>? Closed;
}
TeachingTipPresenter is the default and carries all the WinUI-specific handling, so an app that
wants none of it pays nothing for it.
Storage
Defaults to app-local settings when the app has package identity, and a JSON file under
%LOCALAPPDATA% when it does not. Supply your own ITipStore to put tip state anywhere else — it
is a three-member string-to-string interface.
Debugging
foreach (var d in guide.Inspect())
Debug.WriteLine(d); // command-palette: UserNotIdle (3.4s left) [shown 1]
or drop in the panel:
DebugPane.Content = new TipsInspector { Guide = guide };
It lists every tip, why each is or is not showing, and offers per-tip and global resets. It only polls while it is on screen.
Why it behaves the way it does
See docs/design-notes.md — presence vs engagement vs idleness, refunding
tips that never rendered, why GetLastInputInfo and CompositionTarget.Rendering were both
rejected, and why the package contains no XAML.
License
MIT © Arcadio Garcia
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows10.0.19041 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
-
net8.0-windows10.0.19041
- Microsoft.WindowsAppSDK (>= 1.6.240923002)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.