InkCanvas¶
Header: winui/ui/media/InkCanvas.hpp. Spec: winui/ui/media/InkCanvas.hpp.
A surface the user draws ink on with the pointer.
In Inking mode a press starts a stroke, every move adds a point and the release commits it to strokes(), raising on_strokes_collected(). A press without a move leaves a dot. The stroke is drawn as it grows, each point at the pen's pressure. A mouse always reports 0.5. In Erasing mode the drag removes each stroke it passes over and raises on_strokes_erased(). A pen's eraser end or eraser button does the same in Inking mode, unless set_eraser_input_enabled(false) turns it off. With fit_to_curve, strokes are smoothed into Bézier curves. Highlighter strokes are drawn first and translucent, the rest on top, all clipped to the canvas. The canvas has no background of its own, so put it in a ui::BorderControl for paper.
Only the devices in input_device_types() draw. By default these are the pen and the mouse, which leaves touch to whatever scrolls the canvas. A right-button drag, including a pen with its barrel button held, draws nothing and goes to on_unprocessed_input() (see set_right_drag_action()). So does every press, move and release in None mode. Use that input to build a lasso or another tool of the app's own. A ruler and a protractor, shown with set_ruler() and set_protractor(), lie over the ink to draw straight lines and arcs along. With set_erase_mode(InkCanvasEraseMode::Point) the eraser removes only the ink under it, eraser_size() wide, and leaves the rest of a stroke it crosses as new strokes. strokes() is the data to save, in whatever format the app likes. ui::InkCanvas is the declarative form, and ui::InkToolbarControl is a ready-made set of tools for it.
Example:
auto owned = std::make_unique<ui::InkCanvasControl>();
auto* canvas = owned.get();
canvas->set_drawing_attributes({.color = ui::Color::from_rgba(230, 27, 27), .size = {4.0f, 4.0f}});
canvas->on_strokes_collected([](std::span<const ui::InkStroke> added)
{ std::println("{} new stroke(s)", added.size()); });
window.set_content(std::move(owned));
canvas->set_input_mode(ui::InkCanvasInputMode::Erasing); // the next drag erases
canvas->strokes().clear(); // or wipe it all
ui::InkCanvas¶
A surface the user draws ink on. Builds a ui::InkCanvasControl, whose description says what the pointer does in each mode.
strokes is the drawing it starts with. Read and change the strokes later through the control's InkCanvasControl::strokes(). The canvas has no background of its own, so it usually sits in a ui::Border.
Example:
ui::Control<ui::InkCanvas> canvas;
ui::Property<std::size_t> count{0};
window.set_content(ui::VerticalStack{.spacing = 8, .content = {
ui::Text{.text = ui::bind(count, [](std::size_t n) { return std::format("{} stroke(s)", n); })},
ui::Border{
.background = ui::colors::White,
.border_brush = ui::Color::from_rgba(0, 0, 0, 40),
.border_thickness = ui::Thickness{1.0f},
.height = 280.0f,
.content = ui::InkCanvas{
.drawing_attributes = ui::InkDrawingAttributes{.color = ui::Color::from_rgba(0, 77, 230),
.size = {4.0f, 4.0f}},
.accessibility = {.name = "Drawing canvas"},
.on_strokes_changed = [&] { count = canvas->strokes().size(); },
.control = canvas,
},
},
ui::Button{.text = "Clear", .on_click = [&] { canvas->strokes().clear(); }},
}});
Every field, in the order a designated initializer names them:
ui::InkCanvas{
.drawing_attributes, .input_mode, .input_device_types, .eraser_input_enabled, .erase_mode,
.eraser_size, .right_drag_action, .ruler, .protractor, .strokes, .style, .margin, .width,
.height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
.vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_strokes_collected,
.on_strokes_erased, .on_strokes_changed, .on_unprocessed_input, .on_pointer_entered,
.on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
drawing_attributes |
ui::Binding<ui::InkDrawingAttributes> |
The attributes the next stroke is drawn with. | |
input_mode |
ui::Binding<ui::InkCanvasInputMode> |
{ui::InkCanvasInputMode::Inking} |
What a left-button drag does: draw (the default), erase whole strokes, or nothing. |
input_device_types |
ui::Binding<ui::PointerDeviceTypes> |
{ui::PointerDeviceTypes::Pen \| ui::PointerDeviceTypes::Mouse} |
The devices whose presses draw or erase. The pen and the mouse by default. |
eraser_input_enabled |
ui::Binding<bool> |
{true} |
Whether a pen's eraser end erases in Inking mode. |
erase_mode |
ui::Binding<ui::InkCanvasEraseMode> |
{ui::InkCanvasEraseMode::Stroke} |
What the eraser removes: whole strokes, the default, or only the ink under it. |
eraser_size |
ui::Binding<float> |
{8.0f} |
The point eraser's width, DIPs. |
right_drag_action |
ui::Binding<ui::InkCanvasRightDragAction> |
{ui::InkCanvasRightDragAction::LeaveUnprocessed} |
Whether a right-button drag inks or goes to on_unprocessed_input, the default. |
ruler |
ui::Binding<ui::InkRuler> |
The ruler. Hidden by default. | |
protractor |
ui::Binding<ui::InkProtractor> |
The protractor. Hidden by default. | |
strokes |
std::vector<ui::InkStroke> |
The strokes the canvas starts with. Each gets a fresh id. | |
on_strokes_collected |
ui::Callback<void(std::span<const ui::InkStroke>)> |
The stroke the user drew, when it is committed. | |
on_strokes_erased |
ui::Callback<void(std::span<const ui::InkStroke>)> |
The strokes the eraser removed. | |
on_strokes_changed |
ui::Callback<void()> |
Any change to the strokes: drawn, erased, or changed through the container. | |
on_unprocessed_input |
ui::Callback<void(const ui::PointerEvent&)> |
The pointer input the canvas doesn't turn into ink, in canvas DIPs. | |
on_pointer_entered |
ui::Action |
The pointer moved onto the control. | |
on_pointer_exited |
ui::Action |
The pointer left the control. | |
on_got_focus |
ui::Action |
Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this. | |
on_lost_focus |
ui::Action |
Keyboard focus left the control and everything inside it. | |
on_key_down |
ui::Callback<ui::Handling(const ui::KeyEvent&)> |
A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()). | |
on_wheel |
ui::Callback<ui::Handling(const ui::PointerEvent&)> |
A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()). | |
control |
ui::ControlOut<ui::InkCanvas> |
Out: the handle that receives the built control. |
Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).
build¶
Consumes the spec and returns the built ui::InkCanvasControl; the caller owns it.
ui::InkCanvasControl¶
Derives from ui::Element.
A surface the user draws ink on with the pointer.
In Inking mode a press starts a stroke, every move adds a point and the release commits it to strokes(), raising on_strokes_collected(). A press without a move leaves a dot. The stroke is drawn as it grows, each point at the pen's pressure. A mouse always reports 0.5. In Erasing mode the drag removes each stroke it passes over and raises on_strokes_erased(). A pen's eraser end or eraser button does the same in Inking mode, unless set_eraser_input_enabled(false) turns it off. With fit_to_curve, strokes are smoothed into Bézier curves. Highlighter strokes are drawn first and translucent, the rest on top, all clipped to the canvas. The canvas has no background of its own, so put it in a ui::BorderControl for paper.
Only the devices in input_device_types() draw. By default these are the pen and the mouse, which leaves touch to whatever scrolls the canvas. A right-button drag, including a pen with its barrel button held, draws nothing and goes to on_unprocessed_input() (see set_right_drag_action()). So does every press, move and release in None mode. Use that input to build a lasso or another tool of the app's own. A ruler and a protractor, shown with set_ruler() and set_protractor(), lie over the ink to draw straight lines and arcs along. With set_erase_mode(InkCanvasEraseMode::Point) the eraser removes only the ink under it, eraser_size() wide, and leaves the rest of a stroke it crosses as new strokes. strokes() is the data to save, in whatever format the app likes. ui::InkCanvas is the declarative form, and ui::InkToolbarControl is a ready-made set of tools for it.
Example:
auto owned = std::make_unique<ui::InkCanvasControl>();
auto* canvas = owned.get();
canvas->set_drawing_attributes({.color = ui::Color::from_rgba(230, 27, 27), .size = {4.0f, 4.0f}});
canvas->on_strokes_collected([](std::span<const ui::InkStroke> added)
{ std::println("{} new stroke(s)", added.size()); });
window.set_content(std::move(owned));
canvas->set_input_mode(ui::InkCanvasInputMode::Erasing); // the next drag erases
canvas->strokes().clear(); // or wipe it all
InkCanvasControl¶
An empty canvas in Inking mode with the default drawing attributes.
~InkCanvasControl¶
strokes¶
The committed strokes.
strokes¶
The committed strokes, read-only.
wet_stroke¶
The stroke being drawn right now, or nullptr between strokes. Not yet in strokes().
drawing_attributes¶
What the next stroke is drawn with.
set_drawing_attributes¶
Sets what the next stroke is drawn with. Strokes already drawn keep their own attributes.
input_mode¶
What pointer input does. Inking by default.
set_input_mode¶
Switches what the next press does. A stroke in progress is committed first.
input_device_types¶
The devices whose presses draw or erase. Pen | Mouse by default.
set_input_device_types¶
Sets the devices that draw or erase. Takes effect from the next press. A stroke in progress is finished with the device that started it.
eraser_input_enabled¶
Whether a pen's eraser erases in Inking mode. True by default. When off, the eraser end draws nothing.
set_eraser_input_enabled¶
Turns the pen's eraser on or off. Takes effect from the next press.
erase_mode¶
What the eraser removes: whole strokes, the default, or only the ink under it.
set_erase_mode¶
Sets what the eraser removes. Takes effect from the next eraser move.
eraser_size¶
The point eraser's width in DIPs, 8 by default.
set_eraser_size¶
Sets the point eraser's width, at least 1 DIP.
right_drag_action¶
Whether a right-button drag inks or goes to on_unprocessed_input(). LeaveUnprocessed by default.
set_right_drag_action¶
Sets what a right-button drag does. Takes effect from the next press.
ruler¶
The ruler. Hidden by default.
set_ruler¶
Shows, hides, moves or turns the ruler. Redraws.
protractor¶
The protractor. Hidden by default.
set_protractor¶
Shows, hides, moves or turns the protractor. Redraws.
on_stencils_changed¶
Raised when the user drags or turns the ruler or the protractor.
lasso_outline¶
The outline set_lasso_outline() draws. Empty by default.
set_lasso_outline¶
Draws outline, in canvas DIPs, as a dashed line over the ink and the stencils. Use it to show the trail of a drag that selects strokes with InkStrokeContainer::select_with_polyline(). An empty outline hides it. Redraws.
on_strokes_collected¶
Raised with the stroke the user drew, when it is committed to strokes().
on_strokes_erased¶
Raised with the strokes the eraser removed, after they are gone. The point eraser reports the strokes it cut, and what is left of them stays as new strokes.
on_strokes_changed¶
Raised after any change to strokes(): drawn, erased or changed through the container.
on_unprocessed_input¶
Raised for pointer input the canvas doesn't turn into ink, with the position in canvas DIPs. In None mode that is every press, move and release over the canvas, plus the moves of a drag that left it. In the other modes it is a right-button drag, from press to release, when the right-drag action is InkCanvasRightDragAction::LeaveUnprocessed. A Leave event ends a drag the window lost.
automation_role¶
Exposed to UI Automation as a pane.
measure_override¶
Returns no size of its own. A canvas is as large as its slot, or its width and height, make it.
render_override¶
Draws the highlighter strokes, the other strokes, the wet stroke, the stencils and the lasso outline, clipped to the bounds.
wheel_override¶
Turns the ruler or the protractor under the pointer. Anywhere else the wheel isn't consumed.
pointer_override¶
Left-button drags (pen tip or finger) from input_device_types() draw or erase, as input_mode() and the pen's eraser say. Input that isn't inked goes to on_unprocessed_input().
enabled_changed¶
is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.
class InkStrokeContainer¶
The strokes on a ui::InkCanvasControl. Read them, add, remove, clear or replace them all, and select, move and delete some of them.
Every stroke that goes in gets a fresh id in place of the one it came with, so a set saved with strokes() and handed back to replace() comes back renumbered. Every change, including a change of selection, redraws the canvas and raises InkCanvasControl::on_strokes_changed(). Each canvas owns one container, reached through InkCanvasControl::strokes().
Selection is the basis for a lasso. The canvas reports the pointer input it doesn't turn into ink through InkCanvasControl::on_unprocessed_input(). The app collects the outline, passes it to select_with_polyline(), then moves or deletes the selected strokes.
Example:
// Keep a copy, clear the canvas, and put the drawing back later.
auto canvas = std::make_unique<ui::InkCanvasControl>();
std::vector<ui::InkStroke> saved{canvas->strokes().strokes().begin(), canvas->strokes().strokes().end()};
canvas->strokes().clear();
canvas->strokes().replace(std::move(saved));
InkStrokeContainer¶
InkStrokeContainer¶
operator=¶
strokes¶
Every stroke, oldest first.
size¶
The number of strokes.
empty¶
True when there are no strokes.
stroke_by_id¶
The stroke with id, or nullptr. The pointer lasts until the next change.
add_stroke¶
Appends stroke on top of the others and returns the id it was given. A stroke with no points is dropped and 0 is returned.
add_strokes¶
Appends each stroke in order, as one change.
remove_stroke¶
Removes the stroke with id. Returns false when there is none.
clear¶
Removes every stroke. Does nothing, and raises no change, when there are none.
replace¶
Removes every stroke and adds strokes in their place, as one change. Use it to load a saved drawing.
bounding_rect¶
The union of every stroke's bounding_rect(). Empty when there are none.
select_with_polyline¶
Selects the strokes lying wholly inside the outline points and deselects the rest. The outline is closed back to its first point. Returns the selection's bounding rect, empty when nothing is inside. Pass it the outline of a lasso drawn over the canvas.
select_with_line¶
Selects the strokes the line from from to to crosses and deselects the rest. Returns the selection's bounding rect, empty when it crosses none.
set_selected¶
Selects or deselects the stroke with id. Returns false when there is none.
clear_selection¶
Deselects every stroke.
has_selection¶
Whether any stroke is selected.
selection_bounding_rect¶
The union of the selected strokes' bounding_rect(). Empty when none is selected.
move_selected¶
Moves every selected stroke by translation DIPs. Returns the selection's new bounding rect.
delete_selected¶
Removes every selected stroke. Returns the rect they covered, empty when none was selected.
struct InkStroke¶
One stroke of ink: its points, in drawing order, and how it looks.
A stroke is a plain value. A ui::InkStrokeContainer gives it an id when it is added.
Example:
auto canvas = std::make_unique<ui::InkCanvasControl>();
ui::InkStroke tick{.points = {{.position = {20, 50}}, {.position = {40, 70}}, {.position = {80, 20}}}};
tick.attributes.color = ui::Color::from_rgba(0, 128, 85);
canvas->strokes().add_stroke(std::move(tick));
points¶
The sampled points, in canvas DIPs.
attributes¶
Color, size, nib and the rest.
id¶
Unique within its container. 0 for a stroke in no container.
selected¶
Whether the stroke is selected. A selected stroke is drawn hollow in its own color, and the container's move_selected() and delete_selected() act on it.
bounding_rect¶
The area the stroke paints: its points' bounds grown by half the nib at its widest. An empty rect at the origin for a stroke with no points.
struct InkPoint¶
One sampled point of an ink stroke: where it is, in the canvas's own DIPs (0,0 is the canvas's top-left corner), and how hard the pen pressed there.
Example:
ui::InkStroke line;
line.points = {{.position = {10, 10}}, {.position = {90, 40}, .pressure = 0.9f}};
| Field | Type | Default | |
|---|---|---|---|
position |
Point |
Canvas DIPs. | |
pressure |
float |
0.5f |
0 to 1, the pen's PointerEvent::pressure. A mouse always reports 0.5. |
struct InkDrawingAttributes¶
How a stroke looks. The defaults are a black, round, 2 DIP pen.
size is the nib's width and height. A pressure-sensitive stroke scales it at each point: a quarter of the size at no pressure, the full size at 0.5 and 1.75 times the size at full pressure. ignore_pressure keeps every point at the full size. A highlighter is drawn at half opacity and under the ordinary ink, so writing stays legible over it.
Example:
auto canvas = std::make_unique<ui::InkCanvasControl>();
ui::InkDrawingAttributes marker{
.color = ui::Color::from_rgba(255, 230, 0),
.size = {6.0f, 18.0f},
.pen_tip = ui::InkPenTip::Rectangle,
.draw_as_highlighter = true,
};
canvas->set_drawing_attributes(marker);
color¶
The ink's color.
size¶
The nib's width and height, DIPs.
pen_tip¶
The nib's shape.
draw_as_highlighter¶
Translucent, drawn under other ink.
ignore_pressure¶
Every point at the full size.
fit_to_curve¶
Smooths the points into Bézier curves.
kind¶
Pen or pencil ink.
pencil_opacity¶
Opacity from 0 to 1, for pencil ink only.
for_pencil¶
The defaults with kind set to Pencil.
struct InkRuler¶
The ruler on a ui::InkCanvasControl, a straight edge to draw along.
While it shows, a press on its body drags it and the wheel over it turns it. A stroke started on or close outside one of its long edges is drawn straight along that edge, outside it, wherever the pointer goes. Its scale is in centimeters, at 96 DIPs to the inch, and it shows its angle.
Example:
auto canvas = std::make_unique<ui::InkCanvasControl>();
canvas->set_ruler({.visible = true, .angle = 30.0f}); // centered on the canvas, turned 30 degrees
| Field | Type | Default | |
|---|---|---|---|
visible |
bool |
false |
Shown on the canvas. |
center |
std::optional<Point> |
Its middle, canvas DIPs. nullopt is the canvas's center. | |
angle |
float |
0.0f |
Degrees clockwise from horizontal. |
length |
float |
600.0f |
Its length, DIPs. |
width |
float |
80.0f |
Its width across, DIPs. |
tick_marks |
bool |
true |
The centimeter scale. |
struct InkProtractor¶
The protractor on a ui::InkCanvasControl, a circle to draw arcs along.
While it shows, a press on its body drags it and the wheel over it turns its scale. A stroke started on or close outside its edge is drawn around the circle, outside it.
Example:
auto canvas = std::make_unique<ui::InkCanvasControl>();
canvas->set_protractor({.visible = true, .center = ui::Point{200, 150}, .radius = 120.0f});
| Field | Type | Default | |
|---|---|---|---|
visible |
bool |
false |
Shown on the canvas. |
center |
std::optional<Point> |
Its center, canvas DIPs. nullopt is the canvas's center. | |
angle |
float |
0.0f |
How far the scale is turned clockwise, degrees. |
radius |
float |
160.0f |
Its radius, DIPs. |
tick_marks |
bool |
true |
A degree scale around the edge. |
center_marker |
bool |
true |
A cross at the center. |
angle_readout |
bool |
true |
A readout of its angle under the center. |
Enumerations¶
InkPenTip¶
The shape of the nib a stroke is drawn with.
| Value | |
|---|---|
Circle |
A round nib, elliptical when its width and height differ. Draws smooth, even lines. |
Rectangle |
A flat, axis-aligned nib, like a highlighter's chisel tip. |
InkDrawingAttributesKind¶
Which family of ink a stroke's attributes describe.
| Value | |
|---|---|
Default |
Pen and highlighter ink. |
Pencil |
Pencil ink: lighter, with a grain of specks, at pencil_opacity. |
InkCanvasInputMode¶
What pointer input on a ui::InkCanvasControl does.
| Value | |
|---|---|
None |
Draws and erases nothing. Pointer input goes to on_unprocessed_input(). |
Inking |
The default. A drag draws a stroke with drawing_attributes(), and a pen's eraser erases. |
Erasing |
A left-button drag removes every whole stroke it touches (the stroke eraser). |
InkCanvasRightDragAction¶
What a right-button drag on a ui::InkCanvasControl does. A pen pressed with its barrel button held counts as the right button.
| Value | |
|---|---|
LeaveUnprocessed |
The default. The drag isn't inked and goes to on_unprocessed_input(). |
AllowProcessing |
Inks or erases as a left-button drag does. |
InkCanvasEraseMode¶
What the eraser removes.
| Value | |
|---|---|
Stroke |
The default. Every whole stroke the eraser touches. |
Point |
Only the ink under the eraser. A stroke it crosses splits in two. |
Tests¶
tests/test_ink_canvas.cpp