Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
scripts/build-installer.ps1 both read it from here, so releasing is a reviewed change
to this line rather than an edit in a pipeline variable group.
-->
<VersionPrefix>1.2.0</VersionPrefix>
<VersionPrefix>1.2.1</VersionPrefix>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ How the project is developed and shipped is documented separately:

- Low-latency, pressure-aware WPF wet ink, including rear-eraser detection on any pen that reports it
- A normal cursor for physical mouse input, and a pen-hover indicator that shows what a tap would do: the laser with its halo and speed trail, a dashed square around what the eraser would clear, and a high-contrast dot for everything else. All of them disappear on contact
- Optional mouse drawing (default when Windows reports neither a pen tablet nor a touchscreen): the left button uses the current tool, Ctrl and the left button move and resize a container, and Eraser and Pan appear on the toolbar. A mouse reports no pressure, so ink is drawn at an even width and Calligraphy is the one tool that still varies, because its width comes from speed. Nothing about the pen changes when it is on
- Optional mouse drawing (default when Windows reports neither a pen tablet nor a touchscreen): the left button uses the current tool, Ctrl and the left button move and resize a container, and Eraser and Pan appear on the toolbar. A mouse reports no pressure, so ink is drawn at an even width and Calligraphy is the one tool that still varies, because its width comes from speed. Nothing about the pen changes when it is on. With it off, picking a tool from the toolbar with the mouse offers to turn it on, once a session
- Touch panning and two-finger pinch zoom
- Optional finger drawing (default when no pen is detected): one finger uses the current tool, two fingers still pan and pinch-zoom, and Eraser and Pan appear on the toolbar
- A notice at startup when Windows reports neither a pen tablet nor a touchscreen, saying which pointing device the session is drawing with and what a pen would add. Dismissable from the notice itself or from Preferences, since the tablet list Windows reports can miss a pen that has never been in range
Expand Down Expand Up @@ -296,3 +296,19 @@ touching any mouse handler:
8. Select the Eraser, pan with the right button, and confirm the Eraser is still selected.
9. On a pen machine with Mouse drawing **On**, draw with the pen and confirm nothing about
it changed. This is the regression that matters.

With Mouse drawing **Off**, the offer has its own list. Every one of these is about a key
doing nothing, which is the part a later change is most likely to undo:

1. Pick a tool from the toolbar with the mouse. The tool is selected, and the offer appears
after it rather than instead of it.
2. Pick another tool. The offer does not appear again this session.
3. Press Enter with nothing focused. Nothing happens and the dialog stays open.
4. Tab to the checkbox and press Enter. Still nothing.
5. Tab to a button and press Enter. That button, and only that button, acts.
6. Press Escape. The dialog closes and Mouse drawing stays off.
7. Choose **Enable mouse mode**. The left button draws immediately, and Eraser and Pan
appear on the toolbar.
8. Tick **Don't show me this again**, choose **Cancel**, restart, and confirm the offer is
gone and **Help → Preferences → Input** can bring it back.
9. Reach for the toolbar with the pen on a pen machine, and confirm no offer appears.
5 changes: 3 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The delivery chain works end to end: a merge to `main` builds, signs, and publis
pre-release to GitHub Releases, and one approval promotes that same build to a release.
<https://whiteboard.sqlbi.com> reads its download links from the release manifest
deployed beside it and needs no edit per release. The current product version is `VersionPrefix` in `Directory.Build.props`
(1.2.0). Identity version for the Store package is `VersionPrefix.0` (`1.2.0.0`).
(1.2.1). Identity version for the Store package is `VersionPrefix.0` (`1.2.1.0`).

Declaring that number is decision 20 in [docs/decisions.md](docs/decisions.md). What 1.0
was waiting on shipped during 0.9.x: Preferences, `.wimport`, Explorer and VS Code
Expand All @@ -26,7 +26,8 @@ detected).
No numbered work remains. 1.2.0 answered
[discussion 78](https://github.com/sql-bi/SQLBI-Whiteboard/discussions/78) with Mouse
drawing — decision 23, with the alternatives kept in
[docs/mouse-mode.md](docs/mouse-mode.md). The video teaser is recorded and served from the landing page
[docs/mouse-mode.md](docs/mouse-mode.md) — and 1.2.1 made it discoverable from the
toolbar, decision 24. The video teaser is recorded and served from the landing page
itself as `site/teaser-av1.mp4` / `site/teaser-h264.mp4` — the Vimeo-embed plan was
reversed, see decision 19 in [docs/decisions.md](docs/decisions.md); the production
script and staging assets are in `docs/teaser/`. The release manifests and the Store
Expand Down
35 changes: 35 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,41 @@ pen, which is why **On** is offered and not only the automatic default.

---

## 24. The mouse mode offer has no default button

**Implemented** in 1.2.1.

Decision 23 left Mouse drawing discoverable only in Preferences, which is the one place
someone who does not know the feature exists will not look. The signal that they might want
it is unambiguous and already in the application: **picking a tool from the toolbar with the
mouse.** A pen user reaches for the palette with the pen, so a mouse arriving there is
someone whose next stroke is going to disappoint them. That is when the offer appears.

Three properties of it were chosen rather than inherited, and each is the kind a later
change would quietly undo:

- **Neither button is `IsDefault`, and Enter is swallowed.** This is a question about how
the application behaves, not a confirmation, and the two answers are not
interchangeable — one changes what the left button means. A default button would let
Enter answer it for someone who was typing, and whichever button we picked would be the
wrong one half the time. `Window_PreviewKeyDown` therefore marks every Enter handled
before it can reach a button, and invokes one only when the person has deliberately moved
focus to it. Escape closes, because dismissing is always safe. Nothing is focused when
the dialog opens, so the first Tab reaches the checkbox rather than a primed button.
- **Once a session, and the checkbox is unchecked.** Prompting on every toolbar click would
be intolerable, and prompting once and never again would lose the person who was not
ready to decide. A single Cancel answers this session; the checkbox answers every one
after it, and `Help → Preferences → Input` is the way back from that.
- **The offer is queued, not shown from the click handler.** It is dispatched at background
priority so the click first does what it came to do. The tool is selected, and the dialog
then explains why it may not behave as expected — rather than intercepting the click and
leaving the person unsure whether their tool was chosen at all.

**Enable mouse mode** sets `MouseMode.On` rather than `WhenNoDigitizer`, because the offer
can only have appeared on a machine where the automatic default already decided not to.

---

## Open questions

- arm64 is not built; add it if Surface devices matter for a pen application.
Expand Down
2 changes: 1 addition & 1 deletion site/guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ <h3>Navigation</h3>
<p>The window can sit in a narrow strip beside another app. <kbd>F11</kbd> fills the monitor and hides the title and tabs. <kbd>Ctrl</kbd><span class="plus">+</span><kbd>F11</kbd> hides the same chrome and keeps the window where it is. <kbd>Escape</kbd> leaves either unless a text container is being edited.</p>

<h3>Drawing with a mouse</h3>
<p><span class="ui">Preferences → Mouse drawing</span> turns the left button into the selected tool, and defaults to on where Windows reports neither a pen tablet nor a touchscreen. The tool then stays selected instead of being handed back, and <kbd>Ctrl</kbd> with the left button is how you move and resize a container. A mouse reports no pressure, so ink is drawn at an even width; Calligraphy still varies, because its width comes from speed. Nothing about the pen changes, so this can be left on beside one.</p>
<p><span class="ui">Preferences → Mouse drawing</span> turns the left button into the selected tool, and defaults to on where Windows reports neither a pen tablet nor a touchscreen. The tool then stays selected instead of being handed back, and <kbd>Ctrl</kbd> with the left button is how you move and resize a container. A mouse reports no pressure, so ink is drawn at an even width; Calligraphy still varies, because its width comes from speed. Nothing about the pen changes, so this can be left on beside one. With it off, picking a tool from the toolbar with the mouse offers to turn it on, once a session.</p>

<h3>Containers</h3>
<p>Images, LiveViews, and text blocks are containers. With a mouse, click to move, drag the circular handle to resize while keeping aspect ratio — hold <kbd>Ctrl</kbd> too with <span class="ui">Mouse drawing</span> on. Releasing the mouse returns to the drawing tool you had. Imported images accept PNG, JPEG, BMP, GIF, and SVG, including clipboard paste and Explorer drag-and-drop. An SVG is kept as its markup and redrawn at every size, so enlarging one costs it nothing.</p>
Expand Down
11 changes: 10 additions & 1 deletion src/SQLBI.Whiteboard.Core/Settings/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ public sealed class AppSettings

public MouseMode MouseMode { get; set; } = MouseMode.WhenNoDigitizer;

/// <summary>
/// Whether to offer Mouse drawing the first time in a session that someone
/// picks a tool with the mouse while it is off. Reaching for the toolbar
/// with a mouse is the one moment the application can be sure the question
/// is worth asking, and the setting is what makes the offer refusable for
/// good.
/// </summary>
public bool SuggestMouseMode { get; set; } = true;

public List<string> SnippetFormatOrder { get; set; } = [.. TextLanguageIds.All];

public InkToolSettings Pen { get; set; } = InkToolSettings.From(InkPalettes.DefaultPen);
Expand Down Expand Up @@ -153,7 +162,7 @@ public sealed class AppSettings

public static class AppSettingsSerializer
{
public const int CurrentVersion = 13;
public const int CurrentVersion = 14;

private static readonly JsonSerializerOptions JsonOptions = new()
{
Expand Down
1 change: 1 addition & 0 deletions src/SQLBI.Whiteboard/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
BorderBrush="{StaticResource ToolbarBorderBrush}"
BorderThickness="1"
Background="{StaticResource ToolbarBackgroundBrush}"
PreviewMouseLeftButtonDown="ToolPalette_PreviewMouseLeftButtonDown"
PreviewMouseRightButtonDown="ToolPalette_PreviewMouseRightButtonDown"
PreviewStylusDown="ToolPalette_PreviewStylusDown"
Panel.ZIndex="10">
Expand Down
47 changes: 47 additions & 0 deletions src/SQLBI.Whiteboard/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ private enum PointerAction
private PointerAction _stylusAction;
private PointerAction _mouseAction;
private bool _mouseToolBorrowed;
private bool _mouseModeOffered;
private PointD _lastPanPoint;
private bool _penInContact;
private bool _touchNavigationLocked;
Expand Down Expand Up @@ -1311,6 +1312,52 @@ private void BeginMouseAction(PointD screen)
}
}

// Picking a tool from the toolbar with the mouse is the one moment the
// application can be sure the question is worth asking: a pen user reaches
// for the palette with the pen. The offer is queued rather than shown from
// here, so the click first does what it came to do - the tool is chosen,
// and the dialog then explains why it may not behave as expected.
private void ToolPalette_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.StylusDevice is not null ||
_mouseModeOffered ||
!_settings.SuggestMouseMode ||
IsMouseModeEffective)
{
return;
}

// Asked once a session however many tools are picked afterwards. The
// checkbox on the offer is what answers it for every session after
// this one.
_mouseModeOffered = true;
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(OfferMouseMode));
}

private void OfferMouseMode()
{
var offer = new MouseModeOfferWindow { Owner = this };
offer.ShowDialog();
var changed = false;
if (offer.EnableRequested)
{
_settings.MouseMode = MouseMode.On;
ApplyPointerModes();
changed = true;
}

if (offer.DoNotShowAgain)
{
_settings.SuggestMouseMode = false;
changed = true;
}

if (changed)
{
PersistSettings();
}
}

private void ToolPalette_PreviewMouseRightButtonDown(
object sender,
MouseButtonEventArgs e)
Expand Down
59 changes: 59 additions & 0 deletions src/SQLBI.Whiteboard/MouseModeOfferWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<Window x:Class="SQLBI.Whiteboard.MouseModeOfferWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Draw with a mouse?"
Width="480"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False"
Focusable="True"
FocusManager.IsFocusScope="True"
Background="{DynamicResource ToolbarBackgroundBrush}"
Loaded="Window_Loaded"
PreviewKeyDown="Window_PreviewKeyDown">
<StackPanel Margin="28,24">
<TextBlock FontFamily="Segoe UI"
FontSize="16"
FontWeight="SemiBold"
Foreground="#FF1F2937"
TextWrapping="Wrap"
Text="Draw with a mouse?" />
<TextBlock Margin="0,12,0,0"
FontFamily="Segoe UI"
FontSize="13"
Foreground="#FF374151"
TextWrapping="Wrap"
Text="SQLBI Whiteboard is designed for a pen and a touchscreen. If you would rather use a mouse, Mouse drawing can be turned on in Preferences." />
<TextBlock Margin="0,10,0,0"
FontFamily="Segoe UI"
FontSize="13"
Foreground="#FF374151"
TextWrapping="Wrap"
Text="The left button then uses the selected tool, and Ctrl with the left button moves and resizes a container. A mouse reports no pressure, so ink is drawn at an even width. Nothing about the pen changes." />
<CheckBox x:Name="DoNotShowAgainBox"
Margin="0,20,0,0"
FontFamily="Segoe UI"
FontSize="13"
Foreground="#FF374151"
Content="Don't show me this again" />
<!-- Neither button is IsDefault or IsCancel. This is a question the
person is meant to answer, so Enter must not answer it for them by
landing on whichever button a dialog would normally favour. -->
<StackPanel Margin="0,16,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button x:Name="EnableButton"
MinWidth="140"
MinHeight="30"
Margin="0,0,8,0"
Content="Enable mouse mode"
Click="EnableButton_Click" />
<Button x:Name="CancelButton"
MinWidth="96"
MinHeight="30"
Content="Cancel"
Click="CancelButton_Click" />
</StackPanel>
</StackPanel>
</Window>
75 changes: 75 additions & 0 deletions src/SQLBI.Whiteboard/MouseModeOfferWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SQLBI.Whiteboard;

/// <summary>
/// Offered the first time in a session that someone picks a tool from the
/// toolbar with the mouse while Mouse drawing is off. Reaching for the toolbar
/// with a mouse is the one moment the application can be sure the question is
/// worth asking.
/// </summary>
public partial class MouseModeOfferWindow : Window
{
public MouseModeOfferWindow() => InitializeComponent();

/// <summary>
/// Whether the person asked for Mouse drawing to be turned on.
/// </summary>
public bool EnableRequested { get; private set; }

/// <summary>
/// Whether the offer was declined for good. Unchecked by default: a single
/// Cancel is an answer for this session, not for every one after it.
/// </summary>
public bool DoNotShowAgain => DoNotShowAgainBox.IsChecked == true;

// Nothing starts focused, so the first Tab reaches the checkbox rather than
// a button already primed to act. Without this the checkbox takes focus on
// open, and a stray Space would answer the wrong question.
private void Window_Loaded(object sender, RoutedEventArgs e) => Keyboard.Focus(this);

private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
Close();
e.Handled = true;
return;
}

if (e.Key != Key.Enter)
{
return;
}

// Enter is always handled here, and never reaches a button that WPF
// would otherwise treat as the default. It acts only on a button the
// person has deliberately moved focus to, so the answer is theirs.
e.Handled = true;
if (Keyboard.FocusedElement is not Button focused)
{
return;
}

if (ReferenceEquals(focused, EnableButton))
{
Enable();
}
else if (ReferenceEquals(focused, CancelButton))
{
Close();
}
}

private void EnableButton_Click(object sender, RoutedEventArgs e) => Enable();

private void CancelButton_Click(object sender, RoutedEventArgs e) => Close();

private void Enable()
{
EnableRequested = true;
Close();
}
}
5 changes: 5 additions & 0 deletions src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ private ToggleButton CreateSwitch(SettingDescriptor setting)
{
SettingsCatalog.Ids.StartFullScreen => _settings.StartFullScreen,
SettingsCatalog.Ids.WarnWhenNoDigitizer => _settings.WarnWhenNoDigitizer,
SettingsCatalog.Ids.SuggestMouseMode => _settings.SuggestMouseMode,
SettingsCatalog.Ids.CheckForUpdates => _settings.CheckForUpdates,
_ => false,
},
Expand Down Expand Up @@ -910,6 +911,10 @@ private void SetBoolean(SettingDescriptor setting, bool value)
{
_settings.WarnWhenNoDigitizer = value;
}
else if (setting.Id == SettingsCatalog.Ids.SuggestMouseMode)
{
_settings.SuggestMouseMode = value;
}
else if (setting.Id == SettingsCatalog.Ids.CheckForUpdates)
{
_settings.CheckForUpdates = value;
Expand Down
Loading