[3/3] Highlighter styling (#1490)

This PR finalises the highlighter shape with new colors, sizing, and
perfect freehand options.

The colors are based on our existing colour palette, but take advantage
of wide-gamut displays to make the highlighter highlightier. I used my
[oklch color palette tool to pick the
palette](https://alex.dytry.ch/toys/palette/?palette=%7B%22families%22:%5B%22black%22,%22grey%22,%22white%22,%22green%22,%22light-green%22,%22blue%22,%22light-blue%22,%22violet%22,%22light-violet%22,%22red%22,%22light-red%22,%22orange%22,%22yellow%22%5D,%22shades%22:%5B%22light-mode%22,%22dark-mode%22,%22hl-light%22,%22hl-dark%22%5D,%22colors%22:%5B%5B%5B0.2308,0,null%5D,%5B0.9097,0,null%5D,%5B0.2308,0,null%5D,%5B0.2308,0,null%5D%5D,%5B%5B0.7692,0.0145,248.02%5D,%5B0.6778,0.0118,256.72%5D,%5B0.7692,0.0145,248.02%5D,%5B0.7692,0.0145,248.02%5D%5D,%5B%5B1,0,null%5D,%5B0.2308,0,null%5D,%5B1,0,null%5D,%5B1,0,null%5D%5D,%5B%5B0.5851,0.1227,164.1%5D,%5B0.5319,0.0811,162.23%5D,%5B0.8729,0.2083,173.3%5D,%5B0.5851,0.152,173.3%5D%5D,%5B%5B0.7146,0.1835,146.44%5D,%5B0.6384,0.1262,143.36%5D,%5B0.8603,0.2438,140.11%5D,%5B0.6082,0.2286,140.11%5D%5D,%5B%5B0.5566,0.2082,268.35%5D,%5B0.4961,0.1644,270.65%5D,%5B0.7158,0.173,243.85%5D,%5B0.5573,0.178,243.85%5D%5D,%5B%5B0.718,0.1422,246.06%5D,%5B0.6366,0.1055,250.98%5D,%5B0.8615,0.1896,200.03%5D,%5B0.707,0.161,200.03%5D%5D,%5B%5B0.5783,0.2186,319.15%5D,%5B0.5043,0.1647,315.37%5D,%5B0.728,0.2001,307.45%5D,%5B0.5433,0.2927,307.45%5D%5D,%5B%5B0.7904,0.1516,319.77%5D,%5B0.6841,0.1139,315.99%5D,%5B0.812,0.21,327.8%5D,%5B0.5668,0.281,327.8%5D%5D,%5B%5B0.5928,0.2106,26.53%5D,%5B0.5112,0.1455,26.18%5D,%5B0.7326,0.21,20.59%5D,%5B0.554,0.2461,20.59%5D%5D,%5B%5B0.7563,0.146,21.1%5D,%5B0.6561,0.0982,20.86%5D,%5B0.7749,0.178,6.8%5D,%5B0.5565,0.2454,6.8%5D%5D,%5B%5B0.6851,0.1954,44.57%5D,%5B0.5958,0.1366,46.6%5D,%5B0.8207,0.175,68.62%5D,%5B0.6567,0.164,68.61%5D%5D,%5B%5B0.8503,0.1149,68.95%5D,%5B0.7404,0.0813,72.25%5D,%5B0.8939,0.2137,100.36%5D,%5B0.7776,0.186,100.36%5D%5D%5D%7D&selected=3).
I'm not sure happy about these colors as they are right now - in
particular, i think dark mode looks a bit rubbish and there are a few
colors where the highlight and original version are much too similar
(light-violet & light-red). Black uses yellow (like note shape) and grey
uses light-blue. Exports are forced into srgb color space rather than P3
for maximum compatibility.


![image](https://github.com/tldraw/tldraw/assets/1489520/e3de762b-6ef7-4d17-87db-3e2b71dd8de1)


![image](https://github.com/tldraw/tldraw/assets/1489520/3bd90aa9-bdbc-4a2b-9e56-e3a83a2a877b)



The size of a highlighter stroke is now based on the text size which
works nicely for making the highlighter play well with text:


![image](https://github.com/tldraw/tldraw/assets/1489520/dd3184fc-decd-4db5-90ce-e9cc75edd3d6)


Perfect freehands settings are very similar to the draw tool, but with
the thinning turned way down. There is still some, but it's pretty
minimal.

### The plan
1. initial highlighter shape/tool #1401 
2. sandwich rendering for highlighter shapes #1418
3. shape styling - new colours and sizes, lightweight perfect freehand
changes #1490 **>you are here<**

### Change Type
- [x] `minor` — New Feature

### Test Plan

1. You can find the highlighter tool in the extended toolbar
2. You can activate the highlighter tool by pressing shift-D
3. Highlighter draws nice and vibrantly when over the page background or
frame background
4. Highlighter is less vibrant but still visible when drawn over images
/ other fills
5. Highlighter size should nicely match the corresponding unscaled text
size
6. Exports with highlighter look as expected

### Release Notes

Highlighter pen is here! 🎉🎉🎉

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
alex 2023-06-01 16:34:59 +01:00 committed by GitHub
parent a11a741de4
commit d6085e4ea6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 211 additions and 107 deletions

View file

@ -1,4 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 24.5C8 24 9.3 23 10.5 23C11.3229 23 12.1927 23.1567 12.8195 23.309C13.2406 23.4113 13.6936 23.3064 14 23V23M7.5 24.5L6.5 23.5L5.5 22.5M7.5 24.5L6.5 25.5L3.5 24.5L5.5 22.5M7.5 24.5L5.5 22.5M5.5 22.5C6 22 7 20.7 7 19.5C7 18.6771 6.84326 17.8073 6.69101 17.1805C6.5887 16.7594 6.69357 16.3064 7 16V16M7 16L18.5858 4.41421C19.3668 3.63317 20.6332 3.63317 21.4142 4.41421L25.5858 8.58579C26.3668 9.36684 26.3668 10.6332 25.5858 11.4142L14 23M7 16L14 23" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M13.1024 25.123L26.6953 10.412C27.424 9.62334 27.3999 8.39987 26.6405 7.64054L23.3649 4.36488C22.6034 3.60337 21.3756 3.58158 20.5875 4.31559L5.98401 17.9175M13.1024 25.123L5.98401 17.9175M13.1024 25.123C12.6024 24.9563 11.3025 24.5374 10.1025 24.5374C8.90254 24.5374 7.60254 25.5374 7.10254 26.0374M5.98401 17.9175C6.15068 18.4175 6.60254 19.8374 6.60254 21.0374C6.60254 22.2374 5.60254 23.5374 5.10254 24.0374M7.10254 26.0374L6.10254 25.0374L5.10254 24.0374M7.10254 26.0374L6.10254 27.0374L2 27.1399L5.10254 24.0374" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.5 24.5L5.5 22.5L7.5 24.5L6.5 25.5L3.5 24.5Z" fill="black"/> <path d="M2 26L4 24L6 26L5 27L2 26Z" fill="black"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 764 B

View file

@ -1,3 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6041 25.123L27.1506 9.55414L20.0322 2.34865L4.48573 17.9175M11.6041 25.123L7.78596 26.7594L2.84937 21.7357L4.48573 17.9175M11.6041 25.123L4.48573 17.9175" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 352 B

View file

@ -151,7 +151,6 @@ export function getAssetUrlsByImport(opts?: AssetUrlOptions): {
'tool-frame': string 'tool-frame': string
'tool-hand': string 'tool-hand': string
'tool-highlight': string 'tool-highlight': string
'tool-highlighter': string
'tool-laser': string 'tool-laser': string
'tool-line': string 'tool-line': string
'tool-media': string 'tool-media': string

View file

@ -162,7 +162,6 @@ import iconsToolEraser from './icons/icon/tool-eraser.svg'
import iconsToolFrame from './icons/icon/tool-frame.svg' import iconsToolFrame from './icons/icon/tool-frame.svg'
import iconsToolHand from './icons/icon/tool-hand.svg' import iconsToolHand from './icons/icon/tool-hand.svg'
import iconsToolHighlight from './icons/icon/tool-highlight.svg' import iconsToolHighlight from './icons/icon/tool-highlight.svg'
import iconsToolHighlighter from './icons/icon/tool-highlighter.svg'
import iconsToolLaser from './icons/icon/tool-laser.svg' import iconsToolLaser from './icons/icon/tool-laser.svg'
import iconsToolLine from './icons/icon/tool-line.svg' import iconsToolLine from './icons/icon/tool-line.svg'
import iconsToolMedia from './icons/icon/tool-media.svg' import iconsToolMedia from './icons/icon/tool-media.svg'
@ -394,7 +393,6 @@ export function getAssetUrlsByImport(opts) {
'tool-frame': formatAssetUrl(iconsToolFrame, opts), 'tool-frame': formatAssetUrl(iconsToolFrame, opts),
'tool-hand': formatAssetUrl(iconsToolHand, opts), 'tool-hand': formatAssetUrl(iconsToolHand, opts),
'tool-highlight': formatAssetUrl(iconsToolHighlight, opts), 'tool-highlight': formatAssetUrl(iconsToolHighlight, opts),
'tool-highlighter': formatAssetUrl(iconsToolHighlighter, opts),
'tool-laser': formatAssetUrl(iconsToolLaser, opts), 'tool-laser': formatAssetUrl(iconsToolLaser, opts),
'tool-line': formatAssetUrl(iconsToolLine, opts), 'tool-line': formatAssetUrl(iconsToolLine, opts),
'tool-media': formatAssetUrl(iconsToolMedia, opts), 'tool-media': formatAssetUrl(iconsToolMedia, opts),

View file

@ -151,7 +151,6 @@ export function getAssetUrlsByMetaUrl(opts?: AssetUrlOptions): {
'tool-frame': string 'tool-frame': string
'tool-hand': string 'tool-hand': string
'tool-highlight': string 'tool-highlight': string
'tool-highlighter': string
'tool-laser': string 'tool-laser': string
'tool-line': string 'tool-line': string
'tool-media': string 'tool-media': string

View file

@ -498,10 +498,6 @@ export function getAssetUrlsByMetaUrl(opts) {
new URL('./icons/icon/tool-highlight.svg', import.meta.url).href, new URL('./icons/icon/tool-highlight.svg', import.meta.url).href,
opts opts
), ),
'tool-highlighter': formatAssetUrl(
new URL('./icons/icon/tool-highlighter.svg', import.meta.url).href,
opts
),
'tool-laser': formatAssetUrl( 'tool-laser': formatAssetUrl(
new URL('./icons/icon/tool-laser.svg', import.meta.url).href, new URL('./icons/icon/tool-laser.svg', import.meta.url).href,
opts opts

View file

@ -634,6 +634,7 @@ export const debugFlags: {
logMessages: DebugFlag<never[]>; logMessages: DebugFlag<never[]>;
resetConnectionEveryPing: DebugFlag<boolean>; resetConnectionEveryPing: DebugFlag<boolean>;
debugCursors: DebugFlag<boolean>; debugCursors: DebugFlag<boolean>;
forceSrgb: DebugFlag<boolean>;
}; };
// @internal (undocumented) // @internal (undocumented)

View file

@ -1,3 +1,7 @@
/*
https://alex.dytry.ch/toys/palette/?palette=%7B%22families%22:%5B%22black%22,%22grey%22,%22white%22,%22green%22,%22light-green%22,%22blue%22,%22light-blue%22,%22violet%22,%22light-violet%22,%22red%22,%22light-red%22,%22orange%22,%22yellow%22%5D,%22shades%22:%5B%22light-mode%22,%22dark-mode%22,%22hl-light%22,%22hl-dark%22%5D,%22colors%22:%5B%5B%5B0.2308,0,null%5D,%5B0.9097,0,null%5D,%5B0.2308,0,null%5D,%5B0.2308,0,null%5D%5D,%5B%5B0.7692,0.0145,248.02%5D,%5B0.6778,0.0118,256.72%5D,%5B0.7692,0.0145,248.02%5D,%5B0.7692,0.0145,248.02%5D%5D,%5B%5B1,0,null%5D,%5B0.2308,0,null%5D,%5B1,0,null%5D,%5B1,0,null%5D%5D,%5B%5B0.5851,0.1227,164.1%5D,%5B0.5319,0.0811,162.23%5D,%5B0.8729,0.2083,173.3%5D,%5B0.5851,0.152,173.3%5D%5D,%5B%5B0.7146,0.1835,146.44%5D,%5B0.6384,0.1262,143.36%5D,%5B0.8603,0.2438,140.11%5D,%5B0.6082,0.2286,140.11%5D%5D,%5B%5B0.5566,0.2082,268.35%5D,%5B0.4961,0.1644,270.65%5D,%5B0.7158,0.173,243.85%5D,%5B0.5573,0.178,243.85%5D%5D,%5B%5B0.718,0.1422,246.06%5D,%5B0.6366,0.1055,250.98%5D,%5B0.8615,0.1896,200.03%5D,%5B0.707,0.161,200.03%5D%5D,%5B%5B0.5783,0.2186,319.15%5D,%5B0.5043,0.1647,315.37%5D,%5B0.728,0.2001,307.45%5D,%5B0.5433,0.2927,307.45%5D%5D,%5B%5B0.7904,0.1516,319.77%5D,%5B0.6841,0.1139,315.99%5D,%5B0.812,0.21,327.8%5D,%5B0.5668,0.281,327.8%5D%5D,%5B%5B0.5928,0.2106,26.53%5D,%5B0.5112,0.1455,26.18%5D,%5B0.7326,0.21,20.59%5D,%5B0.554,0.2461,20.59%5D%5D,%5B%5B0.7563,0.146,21.1%5D,%5B0.6561,0.0982,20.86%5D,%5B0.7749,0.178,6.8%5D,%5B0.5565,0.2454,6.8%5D%5D,%5B%5B0.6851,0.1954,44.57%5D,%5B0.5958,0.1366,46.6%5D,%5B0.8207,0.175,68.62%5D,%5B0.6567,0.164,68.61%5D%5D,%5B%5B0.8503,0.1149,68.95%5D,%5B0.7404,0.0813,72.25%5D,%5B0.8939,0.2137,100.36%5D,%5B0.7776,0.186,100.36%5D%5D%5D%7D&selected=3
*/
.tl-container { .tl-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -142,6 +146,21 @@
--palette-violet-pattern: #bd63d3; --palette-violet-pattern: #bd63d3;
--palette-white-pattern: #ffffff; --palette-white-pattern: #ffffff;
--palette-yellow-pattern: #fecb92; --palette-yellow-pattern: #fecb92;
/* for highlighter pen */
--palette-black-highlight: #fddd00;
--palette-grey-highlight: #cbe7f1;
--palette-green-highlight: #00ffc8;
--palette-light-green-highlight: #65f641;
--palette-blue-highlight: #10acff;
--palette-light-blue-highlight: #00f4ff;
--palette-violet-highlight: #c77cff;
--palette-light-violet-highlight: #ff88ff;
--palette-red-highlight: #ff636e;
--palette-light-red-highlight: #ff7fa3;
--palette-orange-highlight: #ffa500;
--palette-yellow-highlight: #fddd00;
--shadow-1: 0px 1px 2px rgba(0, 0, 0, 0.22), 0px 1px 3px rgba(0, 0, 0, 0.09); --shadow-1: 0px 1px 2px rgba(0, 0, 0, 0.22), 0px 1px 3px rgba(0, 0, 0, 0.09);
--shadow-2: 0px 0px 2px rgba(0, 0, 0, 0.12), 0px 2px 3px rgba(0, 0, 0, 0.24), --shadow-2: 0px 0px 2px rgba(0, 0, 0, 0.12), 0px 2px 3px rgba(0, 0, 0, 0.24),
0px 2px 6px rgba(0, 0, 0, 0.1), inset 0px 0px 0px 1px var(--color-panel-contrast); 0px 2px 6px rgba(0, 0, 0, 0.1), inset 0px 0px 0px 1px var(--color-panel-contrast);
@ -210,6 +229,7 @@
--palette-violet-semi: #31293c; --palette-violet-semi: #31293c;
--palette-white-semi: #ffffff; --palette-white-semi: #ffffff;
--palette-yellow-semi: #3c3934; --palette-yellow-semi: #3c3934;
/* for fill style 'pattern' */ /* for fill style 'pattern' */
--palette-black-pattern: #989898; --palette-black-pattern: #989898;
--palette-blue-pattern: #3a4b9e; --palette-blue-pattern: #3a4b9e;
@ -224,6 +244,21 @@
--palette-violet-pattern: #763a8b; --palette-violet-pattern: #763a8b;
--palette-white-pattern: #ffffff; --palette-white-pattern: #ffffff;
--palette-yellow-pattern: #fecb92; --palette-yellow-pattern: #fecb92;
/* for highlighter pen */
--palette-black-highlight: #d2b700;
--palette-grey-highlight: #9cb4cb;
--palette-green-highlight: #009774;
--palette-light-green-highlight: #00a000;
--palette-blue-highlight: #0079d2;
--palette-light-blue-highlight: #00bdc8;
--palette-violet-highlight: #9e00ee;
--palette-light-violet-highlight: #c400c7;
--palette-red-highlight: #de002c;
--palette-light-red-highlight: #db005b;
--palette-orange-highlight: #d07a00;
--palette-yellow-highlight: #d2b700;
--shadow-1: 0px 1px 2px #00000029, 0px 1px 3px #00000038, --shadow-1: 0px 1px 2px #00000029, 0px 1px 3px #00000038,
inset 0px 0px 0px 1px var(--color-panel-contrast); inset 0px 0px 0px 1px var(--color-panel-contrast);
--shadow-2: 0px 1px 3px #00000077, 0px 2px 6px #00000055, --shadow-2: 0px 1px 3px #00000077, 0px 2px 6px #00000055,
@ -232,6 +267,41 @@
inset 0px 0px 0px 1px var(--color-panel-contrast); inset 0px 0px 0px 1px var(--color-panel-contrast);
} }
/** p3 colors */
@media (color-gamut: p3) {
.tl-theme__light:not(.tl-theme__force-sRGB) {
/* for highlighter pen */
--palette-black-highlight: color(display-p3 0.972 0.8705 0.05);
--palette-grey-highlight: color(display-p3 0.8163 0.9023 0.9416);
--palette-green-highlight: color(display-p3 0.2536 0.984 0.7981);
--palette-light-green-highlight: color(display-p3 0.563 0.9495 0.3857);
--palette-blue-highlight: color(display-p3 0.308 0.6632 0.9996);
--palette-light-blue-highlight: color(display-p3 0.1512 0.9414 0.9996);
--palette-violet-highlight: color(display-p3 0.7469 0.5089 0.9995);
--palette-light-violet-highlight: color(display-p3 0.9676 0.5652 0.9999);
--palette-red-highlight: color(display-p3 0.9992 0.4376 0.45);
--palette-light-red-highlight: color(display-p3 0.9988 0.5301 0.6397);
--palette-orange-highlight: color(display-p3 0.9988 0.6905 0.266);
--palette-yellow-highlight: color(display-p3 0.972 0.8705 0.05);
}
.tl-theme__dark:not(.tl-theme__force-sRGB) {
/* for highlighter pen */
--palette-black-highlight: color(display-p3 0.8078 0.7225 0.0312);
--palette-grey-highlight: color(display-p3 0.6299 0.7012 0.7856);
--palette-green-highlight: color(display-p3 0.0085 0.582 0.4604);
--palette-light-green-highlight: color(display-p3 0.2711 0.6172 0.0195);
--palette-blue-highlight: color(display-p3 0.0032 0.4655 0.7991);
--palette-light-blue-highlight: color(display-p3 0.0023 0.7259 0.7735);
--palette-violet-highlight: color(display-p3 0.5651 0.0079 0.8986);
--palette-light-violet-highlight: color(display-p3 0.7024 0.0403 0.753);
--palette-red-highlight: color(display-p3 0.7978 0.0509 0.2035);
--palette-light-red-highlight: color(display-p3 0.7849 0.0585 0.3589);
--palette-orange-highlight: color(display-p3 0.7699 0.4937 0.0085);
--palette-yellow-highlight: color(display-p3 0.8078 0.7225 0.0312);
}
}
.tl-counter-scaled { .tl-counter-scaled {
transform: scale(var(--tl-scale)); transform: scale(var(--tl-scale));
transform-origin: top left; transform-origin: top left;

View file

@ -5617,7 +5617,9 @@ export class App extends EventEmitter<TLEventMap> {
// Get the styles from the container. We'll use these to pull out colors etc. // Get the styles from the container. We'll use these to pull out colors etc.
// NOTE: We can force force a light theme here becasue we don't want export // NOTE: We can force force a light theme here becasue we don't want export
const fakeContainerEl = document.createElement('div') const fakeContainerEl = document.createElement('div')
fakeContainerEl.className = `tl-container tl-theme__${darkMode ? 'dark' : 'light'}` fakeContainerEl.className = `tl-container tl-theme__${
darkMode ? 'dark' : 'light'
} tl-theme__force-sRGB`
document.body.appendChild(fakeContainerEl) document.body.appendChild(fakeContainerEl)
const containerStyle = getComputedStyle(fakeContainerEl) const containerStyle = getComputedStyle(fakeContainerEl)
@ -5642,6 +5644,12 @@ export class App extends EventEmitter<TLEventMap> {
containerStyle.getPropertyValue(`--palette-${color.id}-semi`), containerStyle.getPropertyValue(`--palette-${color.id}-semi`),
]) ])
) as Record<TLColorType, string>, ) as Record<TLColorType, string>,
highlight: Object.fromEntries(
STYLES.color.map((color) => [
color.id,
containerStyle.getPropertyValue(`--palette-${color.id}-highlight`),
])
) as Record<TLColorType, string>,
text: containerStyle.getPropertyValue(`--color-text`), text: containerStyle.getPropertyValue(`--color-text`),
background: containerStyle.getPropertyValue(`--color-background`), background: containerStyle.getPropertyValue(`--color-background`),
solid: containerStyle.getPropertyValue(`--palette-solid`), solid: containerStyle.getPropertyValue(`--palette-solid`),

View file

@ -23,10 +23,10 @@ import { getDrawShapeStrokeDashArray, getFreehandOptions, getPointsFromSegments
export class TLDrawUtil extends TLShapeUtil<TLDrawShape> { export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
static override type = 'draw' static override type = 'draw'
hideResizeHandles = (shape: TLDrawShape) => this.getIsDot(shape) hideResizeHandles = (shape: TLDrawShape) => getIsDot(shape)
hideRotateHandle = (shape: TLDrawShape) => this.getIsDot(shape) hideRotateHandle = (shape: TLDrawShape) => getIsDot(shape)
hideSelectionBoundsBg = (shape: TLDrawShape) => this.getIsDot(shape) hideSelectionBoundsBg = (shape: TLDrawShape) => getIsDot(shape)
hideSelectionBoundsFg = (shape: TLDrawShape) => this.getIsDot(shape) hideSelectionBoundsFg = (shape: TLDrawShape) => getIsDot(shape)
override defaultProps(): TLDrawShape['props'] { override defaultProps(): TLDrawShape['props'] {
return { return {
@ -44,10 +44,6 @@ export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
isClosed = (shape: TLDrawShape) => shape.props.isClosed isClosed = (shape: TLDrawShape) => shape.props.isClosed
private getIsDot(shape: TLDrawShape) {
return shape.props.segments.length === 1 && shape.props.segments[0].points.length < 2
}
getBounds(shape: TLDrawShape) { getBounds(shape: TLDrawShape) {
return Box2d.FromPoints(this.outline(shape)) return Box2d.FromPoints(this.outline(shape))
} }
@ -310,3 +306,7 @@ function getDot(point: VecLike, sw: number) {
r * 2 r * 2
},0` },0`
} }
function getIsDot(shape: TLDrawShape) {
return shape.props.segments.length === 1 && shape.props.segments[0].points.length < 2
}

View file

@ -36,6 +36,21 @@ const solidSettings = (strokeWidth: number): StrokeOptions => {
} }
} }
export function getHighlightFreehandSettings(
strokeWidth: number,
showAsComplete: boolean
): StrokeOptions {
return {
size: 1 + strokeWidth,
thinning: 0.1,
streamline: 0.1, // 0.62 + ((1 + strokeWidth) / 8) * 0.06,
smoothing: 0.5,
simulatePressure: true,
easing: EASINGS.easeOutSine,
last: showAsComplete,
}
}
export function getFreehandOptions( export function getFreehandOptions(
shapeProps: { dash: TLDashType; isPen: boolean; isComplete: boolean }, shapeProps: { dash: TLDashType; isPen: boolean; isComplete: boolean },
strokeWidth: number, strokeWidth: number,

View file

@ -11,24 +11,25 @@ import {
import { TLDrawShapeSegment, TLHighlightShape } from '@tldraw/tlschema' import { TLDrawShapeSegment, TLHighlightShape } from '@tldraw/tlschema'
import { last, rng } from '@tldraw/utils' import { last, rng } from '@tldraw/utils'
import { SVGContainer } from '../../../components/SVGContainer' import { SVGContainer } from '../../../components/SVGContainer'
import { FONT_SIZES } from '../../../constants'
import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg' import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg'
import { App } from '../../App'
import { ShapeFill } from '../shared/ShapeFill' import { ShapeFill } from '../shared/ShapeFill'
import { TLExportColors } from '../shared/TLExportColors' import { TLExportColors } from '../shared/TLExportColors'
import { useForceSolid } from '../shared/useForceSolid' import { useForceSolid } from '../shared/useForceSolid'
import { getFreehandOptions, getPointsFromSegments } from '../TLDrawUtil/getPath' import { getHighlightFreehandSettings, getPointsFromSegments } from '../TLDrawUtil/getPath'
import { OnResizeHandler, TLShapeUtil } from '../TLShapeUtil' import { OnResizeHandler, TLShapeUtil } from '../TLShapeUtil'
const OVERLAY_OPACITY = 0.4 const OVERLAY_OPACITY = 0.35
const UNDERLAY_OPACITY = 0.82
/** @public */ /** @public */
export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> { export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
static type = 'highlight' static type = 'highlight'
hideResizeHandles = (shape: TLHighlightShape) => this.getIsDot(shape) hideResizeHandles = (shape: TLHighlightShape) => getIsDot(shape)
hideRotateHandle = (shape: TLHighlightShape) => this.getIsDot(shape) hideRotateHandle = (shape: TLHighlightShape) => getIsDot(shape)
hideSelectionBoundsBg = (shape: TLHighlightShape) => this.getIsDot(shape) hideSelectionBoundsBg = (shape: TLHighlightShape) => getIsDot(shape)
hideSelectionBoundsFg = (shape: TLHighlightShape) => this.getIsDot(shape) hideSelectionBoundsFg = (shape: TLHighlightShape) => getIsDot(shape)
override defaultProps(): TLHighlightShape['props'] { override defaultProps(): TLHighlightShape['props'] {
return { return {
@ -41,10 +42,6 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
} }
} }
private getIsDot(shape: TLHighlightShape) {
return shape.props.segments.length === 1 && shape.props.segments[0].points.length < 2
}
getBounds(shape: TLHighlightShape) { getBounds(shape: TLHighlightShape) {
return Box2d.FromPoints(this.outline(shape)) return Box2d.FromPoints(this.outline(shape))
} }
@ -60,7 +57,7 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
hitTestPoint(shape: TLHighlightShape, point: VecLike): boolean { hitTestPoint(shape: TLHighlightShape, point: VecLike): boolean {
const outline = this.outline(shape) const outline = this.outline(shape)
const zoomLevel = this.app.zoomLevel const zoomLevel = this.app.zoomLevel
const offsetDist = this.app.getStrokeWidth(shape.props.size) / zoomLevel const offsetDist = getStrokeWidth(shape) / zoomLevel
if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) { if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) {
if (shape.props.segments[0].points.some((pt) => Vec2d.Dist(point, pt) < offsetDist * 1.5)) { if (shape.props.segments[0].points.some((pt) => Vec2d.Dist(point, pt) < offsetDist * 1.5)) {
@ -85,7 +82,7 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) { if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) {
const zoomLevel = this.app.zoomLevel const zoomLevel = this.app.zoomLevel
const offsetDist = this.app.getStrokeWidth(shape.props.size) / zoomLevel const offsetDist = getStrokeWidth(shape) / zoomLevel
if ( if (
shape.props.segments[0].points.some( shape.props.segments[0].points.some(
@ -106,16 +103,28 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
} }
render(shape: TLHighlightShape) { render(shape: TLHighlightShape) {
return <HighlightRenderer app={this.app} shape={shape} opacity={OVERLAY_OPACITY} /> return (
<HighlightRenderer
strokeWidth={getStrokeWidth(shape)}
shape={shape}
opacity={OVERLAY_OPACITY}
/>
)
} }
renderBackground(shape: TLHighlightShape) { renderBackground(shape: TLHighlightShape) {
return <HighlightRenderer app={this.app} shape={shape} /> return (
<HighlightRenderer
strokeWidth={getStrokeWidth(shape)}
shape={shape}
opacity={UNDERLAY_OPACITY}
/>
)
} }
indicator(shape: TLHighlightShape) { indicator(shape: TLHighlightShape) {
const forceSolid = useForceSolid() const forceSolid = useForceSolid()
const strokeWidth = this.app.getStrokeWidth(shape.props.size) const strokeWidth = getStrokeWidth(shape)
const allPointsFromSegments = getPointsFromSegments(shape.props.segments) const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
let sw = strokeWidth let sw = strokeWidth
@ -124,27 +133,33 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
} }
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight' const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
const options = getFreehandOptions( const options = getHighlightFreehandSettings(strokeWidth, showAsComplete)
{ dash: 'draw', isComplete: shape.props.isComplete, isPen: shape.props.isPen },
sw,
showAsComplete,
true
)
const strokePoints = getStrokePoints(allPointsFromSegments, options) const strokePoints = getStrokePoints(allPointsFromSegments, options)
const solidStrokePath =
strokePoints.length > 1
? getSvgPathFromStrokePoints(strokePoints, false)
: getDot(allPointsFromSegments[0], sw)
return <path d={solidStrokePath} /> let strokePath
if (strokePoints.length < 2) {
strokePath = getIndicatorDot(allPointsFromSegments[0], sw)
} else {
strokePath = getSvgPathFromStrokePoints(strokePoints, false)
} }
toSvg(shape: TLHighlightShape, _font: string | undefined, colors: TLExportColors) { return <path d={strokePath} />
return highlighterToSvg(this.app, shape, OVERLAY_OPACITY, colors)
} }
toBackgroundSvg(shape: TLHighlightShape, font: string | undefined, colors: TLExportColors) { override expandSelectionOutlinePx(shape: TLHighlightShape): number {
return highlighterToSvg(this.app, shape, 1, colors) return getStrokeWidth(shape) / 2
}
override toSvg(shape: TLHighlightShape, _font: string | undefined, colors: TLExportColors) {
return highlighterToSvg(getStrokeWidth(shape), shape, OVERLAY_OPACITY, colors)
}
override toBackgroundSvg(
shape: TLHighlightShape,
font: string | undefined,
colors: TLExportColors
) {
return highlighterToSvg(getStrokeWidth(shape), shape, UNDERLAY_OPACITY, colors)
} }
override onResize: OnResizeHandler<TLHighlightShape> = (shape, info) => { override onResize: OnResizeHandler<TLHighlightShape> = (shape, info) => {
@ -171,30 +186,32 @@ export class TLHighlightUtil extends TLShapeUtil<TLHighlightShape> {
}, },
} }
} }
expandSelectionOutlinePx(shape: TLHighlightShape): number {
return (this.app.getStrokeWidth(shape.props.size) * 1.6) / 2
}
} }
function getDot(point: VecLike, sw: number) { function getShapeDot(point: VecLike) {
const r = (sw + 1) * 0.5 const r = 0.1
return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${
r * 2
},0`
}
function getIndicatorDot(point: VecLike, sw: number) {
const r = sw / 2
return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${ return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${
r * 2 r * 2
},0` },0`
} }
function HighlightRenderer({ function HighlightRenderer({
app, strokeWidth,
shape, shape,
opacity, opacity,
}: { }: {
app: App strokeWidth: number
shape: TLHighlightShape shape: TLHighlightShape
opacity?: number opacity?: number
}) { }) {
const forceSolid = useForceSolid() const forceSolid = useForceSolid()
const strokeWidth = app.getStrokeWidth(shape.props.size)
const allPointsFromSegments = getPointsFromSegments(shape.props.segments) const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight' const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
@ -204,30 +221,27 @@ function HighlightRenderer({
sw += rng(shape.id)() * (strokeWidth / 6) sw += rng(shape.id)() * (strokeWidth / 6)
} }
const options = getFreehandOptions( const options = getHighlightFreehandSettings(sw, showAsComplete)
{ isComplete: shape.props.isComplete, isPen: shape.props.isPen, dash: 'draw' },
sw,
showAsComplete,
forceSolid
)
const strokePoints = getStrokePoints(allPointsFromSegments, options) const strokePoints = getStrokePoints(allPointsFromSegments, options)
const solidStrokePath = const solidStrokePath =
strokePoints.length > 1 strokePoints.length > 1
? getSvgPathFromStrokePoints(strokePoints, false) ? getSvgPathFromStrokePoints(strokePoints, false)
: getDot(allPointsFromSegments[0], sw) : getShapeDot(allPointsFromSegments[0])
if (!forceSolid || strokePoints.length < 2) { if (!forceSolid || strokePoints.length < 2) {
setStrokePointRadii(strokePoints, options) setStrokePointRadii(strokePoints, options)
const strokeOutlinePoints = getStrokeOutlinePoints(strokePoints, options)
return ( return (
<SVGContainer id={shape.id} style={{ opacity }}> <SVGContainer id={shape.id} style={{ opacity }}>
<ShapeFill fill="none" color={shape.props.color} d={solidStrokePath} />
<path <path
d={getSvgPathFromStroke(strokeOutlinePoints, true)} d={solidStrokePath}
strokeLinecap="round" strokeLinecap="round"
fill="currentColor" fill="none"
pointerEvents="all"
stroke={`var(--palette-${shape.props.color}-highlight)`}
strokeWidth={sw}
/> />
</SVGContainer> </SVGContainer>
) )
@ -240,7 +254,7 @@ function HighlightRenderer({
d={solidStrokePath} d={solidStrokePath}
strokeLinecap="round" strokeLinecap="round"
fill="none" fill="none"
stroke="currentColor" stroke={`var(--palette-${shape.props.color}-highlight)`}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDashoffset="0" strokeDashoffset="0"
/> />
@ -249,14 +263,13 @@ function HighlightRenderer({
} }
function highlighterToSvg( function highlighterToSvg(
app: App, strokeWidth: number,
shape: TLHighlightShape, shape: TLHighlightShape,
opacity: number, opacity: number,
colors: TLExportColors colors: TLExportColors
) { ) {
const { color } = shape.props const { color } = shape.props
const strokeWidth = app.getStrokeWidth(shape.props.size)
const allPointsFromSegments = getPointsFromSegments(shape.props.segments) const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight' const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
@ -266,12 +279,7 @@ function highlighterToSvg(
sw += rng(shape.id)() * (strokeWidth / 6) sw += rng(shape.id)() * (strokeWidth / 6)
} }
const options = getFreehandOptions( const options = getHighlightFreehandSettings(sw, showAsComplete)
{ dash: 'draw', isComplete: shape.props.isComplete, isPen: shape.props.isPen },
sw,
showAsComplete,
false
)
const strokePoints = getStrokePoints(allPointsFromSegments, options) const strokePoints = getStrokePoints(allPointsFromSegments, options)
setStrokePointRadii(strokePoints, options) setStrokePointRadii(strokePoints, options)
@ -279,9 +287,17 @@ function highlighterToSvg(
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path') const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
path.setAttribute('d', getSvgPathFromStroke(strokeOutlinePoints, true)) path.setAttribute('d', getSvgPathFromStroke(strokeOutlinePoints, true))
path.setAttribute('fill', colors.fill[color]) path.setAttribute('fill', colors.highlight[color])
path.setAttribute('stroke-linecap', 'round') path.setAttribute('stroke-linecap', 'round')
path.setAttribute('opacity', opacity.toString()) path.setAttribute('opacity', opacity.toString())
return path return path
} }
function getStrokeWidth(shape: TLHighlightShape) {
return FONT_SIZES[shape.props.size] * 1.12
}
function getIsDot(shape: TLHighlightShape) {
return shape.props.segments.length === 1 && shape.props.segments[0].points.length < 2
}

View file

@ -4,6 +4,7 @@ export type TLExportColors = {
fill: Record<TLColorType, string> fill: Record<TLColorType, string>
pattern: Record<TLColorType, string> pattern: Record<TLColorType, string>
semi: Record<TLColorType, string> semi: Record<TLColorType, string>
highlight: Record<TLColorType, string>
solid: string solid: string
text: string text: string
background: string background: string

View file

@ -2,11 +2,11 @@ import { TLEventHandlers } from '../types/event-types'
import { StateNode } from './StateNode' import { StateNode } from './StateNode'
import { TLArrowTool } from './TLArrowTool/TLArrowTool' import { TLArrowTool } from './TLArrowTool/TLArrowTool'
import { TLDrawTool } from './TLDrawTool/TLDrawTool' import { TLDrawTool } from './TLDrawTool/TLDrawTool'
import { TLHighlightTool } from './TLDrawTool/TLHighlightTool'
import { TLEraserTool } from './TLEraserTool/TLEraserTool' import { TLEraserTool } from './TLEraserTool/TLEraserTool'
import { TLFrameTool } from './TLFrameTool/TLFrameTool' import { TLFrameTool } from './TLFrameTool/TLFrameTool'
import { TLGeoTool } from './TLGeoTool/TLGeoTool' import { TLGeoTool } from './TLGeoTool/TLGeoTool'
import { TLHandTool } from './TLHandTool/TLHandTool' import { TLHandTool } from './TLHandTool/TLHandTool'
import { TLHighlightTool } from './TLHighlightTool/TLHighlightTool'
import { TLLaserTool } from './TLLaserTool/TLLaserTool' import { TLLaserTool } from './TLLaserTool/TLLaserTool'
import { TLLineTool } from './TLLineTool/TLLineTool' import { TLLineTool } from './TLLineTool/TLLineTool'
import { TLNoteTool } from './TLNoteTool/TLNoteTool' import { TLNoteTool } from './TLNoteTool/TLNoteTool'

View file

@ -1,8 +1,9 @@
import { TLStyleType } from '@tldraw/tlschema' import { TLStyleType } from '@tldraw/tlschema'
import { StateNode } from '../StateNode' import { StateNode } from '../StateNode'
import { Drawing } from './children/Drawing' // shared custody
import { Idle } from './children/Idle' import { Drawing } from '../TLDrawTool/children/Drawing'
import { Idle } from '../TLDrawTool/children/Idle'
export class TLHighlightTool extends StateNode { export class TLHighlightTool extends StateNode {
static override id = 'highlight' static override id = 'highlight'

View file

@ -116,9 +116,7 @@ export const Shape = track(function Shape({
data-shape-type={shape.type} data-shape-type={shape.type}
draggable={false} draggable={false}
> >
{isCulled ? ( {!isCulled && (
<CulledShape shape={shape} util={util} />
) : (
<OptionalErrorBoundary <OptionalErrorBoundary
fallback={ShapeErrorFallback ? (error) => <ShapeErrorFallback error={error} /> : null} fallback={ShapeErrorFallback ? (error) => <ShapeErrorFallback error={error} /> : null}
onError={(error) => onError={(error) =>

View file

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { useValue } from 'signia-react' import { useValue } from 'signia-react'
import { debugFlags } from '../utils/debug-flags'
import { useApp } from './useApp' import { useApp } from './useApp'
import { useContainer } from './useContainer' import { useContainer } from './useContainer'
@ -7,6 +8,7 @@ export function useDarkMode() {
const app = useApp() const app = useApp()
const container = useContainer() const container = useContainer()
const isDarkMode = useValue('isDarkMode', () => app.isDarkMode, [app]) const isDarkMode = useValue('isDarkMode', () => app.isDarkMode, [app])
const forceSrgb = useValue(debugFlags.forceSrgb)
React.useEffect(() => { React.useEffect(() => {
if (isDarkMode) { if (isDarkMode) {
@ -24,5 +26,10 @@ export function useDarkMode() {
color: 'black', color: 'black',
}) })
} }
}, [app, container, isDarkMode]) if (forceSrgb) {
container.classList.add('tl-theme__force-sRGB')
} else {
container.classList.remove('tl-theme__force-sRGB')
}
}, [app, container, forceSrgb, isDarkMode])
} }

View file

@ -11,7 +11,7 @@ export const featureFlags = {
// todo: remove this. it's not used, but we only have one feature flag and i // todo: remove this. it's not used, but we only have one feature flag and i
// wanted an example :( // wanted an example :(
peopleMenu: createFeatureFlag('peopleMenu'), peopleMenu: createFeatureFlag('peopleMenu'),
highlighterTool: createFeatureFlag('highlighterTool'), highlighterTool: createFeatureFlag('highlighterTool', { all: true }),
} satisfies Record<string, DebugFlag<boolean>> } satisfies Record<string, DebugFlag<boolean>>
/** @internal */ /** @internal */
@ -51,6 +51,7 @@ export const debugFlags = {
debugCursors: createDebugValue('debugCursors', { debugCursors: createDebugValue('debugCursors', {
defaults: { all: false }, defaults: { all: false },
}), }),
forceSrgb: createDebugValue('forceSrgbColors', { defaults: { all: false } }),
} }
declare global { declare global {

View file

@ -732,10 +732,10 @@ export type TLUiEventHandler<T extends keyof TLUiEventMap = keyof TLUiEventMap>
export type TLUiEventSource = 'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'toolbar' | 'unknown' | 'zoom-menu'; export type TLUiEventSource = 'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'toolbar' | 'unknown' | 'zoom-menu';
// @public (undocumented) // @public (undocumented)
export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-highlighter' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out';
// @public (undocumented) // @public (undocumented)
export const TLUiIconTypes: readonly ["align-bottom-center", "align-bottom-left", "align-bottom-right", "align-bottom", "align-center-center", "align-center-horizontal", "align-center-left", "align-center-right", "align-center-vertical", "align-left", "align-right", "align-top-center", "align-top-left", "align-top-right", "align-top", "arrow-left", "arrowhead-arrow", "arrowhead-bar", "arrowhead-diamond", "arrowhead-dot", "arrowhead-none", "arrowhead-square", "arrowhead-triangle-inverted", "arrowhead-triangle", "aspect-ratio", "avatar", "blob", "bring-forward", "bring-to-front", "check", "checkbox-checked", "checkbox-empty", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "chevrons-ne", "chevrons-sw", "clipboard-copied", "clipboard-copy", "code", "collab", "color", "comment", "cross-2", "cross", "dash-dashed", "dash-dotted", "dash-draw", "dash-solid", "discord", "distribute-horizontal", "distribute-vertical", "dot", "dots-horizontal", "dots-vertical", "drag-handle-dots", "duplicate", "edit", "external-link", "file", "fill-none", "fill-pattern", "fill-semi", "fill-solid", "follow", "following", "font-draw", "font-mono", "font-sans", "font-serif", "geo-arrow-down", "geo-arrow-left", "geo-arrow-right", "geo-arrow-up", "geo-check-box", "geo-diamond", "geo-ellipse", "geo-hexagon", "geo-octagon", "geo-oval", "geo-pentagon", "geo-rectangle", "geo-rhombus-2", "geo-rhombus", "geo-star", "geo-trapezoid", "geo-triangle", "geo-x-box", "github", "group", "hidden", "image", "info-circle", "leading", "link", "lock-small", "lock", "menu", "minus", "mixed", "pack", "page", "plus", "question-mark-circle", "question-mark", "redo", "reset-zoom", "rotate-ccw", "rotate-cw", "ruler", "search", "send-backward", "send-to-back", "settings-horizontal", "settings-vertical-1", "settings-vertical", "share-1", "share-2", "size-extra-large", "size-large", "size-medium", "size-small", "spline-cubic", "spline-line", "stack-horizontal", "stack-vertical", "stretch-horizontal", "stretch-vertical", "text-align-center", "text-align-justify", "text-align-left", "text-align-right", "tool-arrow", "tool-embed", "tool-eraser", "tool-frame", "tool-hand", "tool-highlight", "tool-highlighter", "tool-laser", "tool-line", "tool-media", "tool-note", "tool-pencil", "tool-pointer", "tool-text", "trash", "triangle-down", "triangle-up", "twitter", "undo", "ungroup", "unlock-small", "unlock", "vertical-align-center", "vertical-align-end", "vertical-align-start", "visible", "warning-triangle", "zoom-in", "zoom-out"]; export const TLUiIconTypes: readonly ["align-bottom-center", "align-bottom-left", "align-bottom-right", "align-bottom", "align-center-center", "align-center-horizontal", "align-center-left", "align-center-right", "align-center-vertical", "align-left", "align-right", "align-top-center", "align-top-left", "align-top-right", "align-top", "arrow-left", "arrowhead-arrow", "arrowhead-bar", "arrowhead-diamond", "arrowhead-dot", "arrowhead-none", "arrowhead-square", "arrowhead-triangle-inverted", "arrowhead-triangle", "aspect-ratio", "avatar", "blob", "bring-forward", "bring-to-front", "check", "checkbox-checked", "checkbox-empty", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "chevrons-ne", "chevrons-sw", "clipboard-copied", "clipboard-copy", "code", "collab", "color", "comment", "cross-2", "cross", "dash-dashed", "dash-dotted", "dash-draw", "dash-solid", "discord", "distribute-horizontal", "distribute-vertical", "dot", "dots-horizontal", "dots-vertical", "drag-handle-dots", "duplicate", "edit", "external-link", "file", "fill-none", "fill-pattern", "fill-semi", "fill-solid", "follow", "following", "font-draw", "font-mono", "font-sans", "font-serif", "geo-arrow-down", "geo-arrow-left", "geo-arrow-right", "geo-arrow-up", "geo-check-box", "geo-diamond", "geo-ellipse", "geo-hexagon", "geo-octagon", "geo-oval", "geo-pentagon", "geo-rectangle", "geo-rhombus-2", "geo-rhombus", "geo-star", "geo-trapezoid", "geo-triangle", "geo-x-box", "github", "group", "hidden", "image", "info-circle", "leading", "link", "lock-small", "lock", "menu", "minus", "mixed", "pack", "page", "plus", "question-mark-circle", "question-mark", "redo", "reset-zoom", "rotate-ccw", "rotate-cw", "ruler", "search", "send-backward", "send-to-back", "settings-horizontal", "settings-vertical-1", "settings-vertical", "share-1", "share-2", "size-extra-large", "size-large", "size-medium", "size-small", "spline-cubic", "spline-line", "stack-horizontal", "stack-vertical", "stretch-horizontal", "stretch-vertical", "text-align-center", "text-align-justify", "text-align-left", "text-align-right", "tool-arrow", "tool-embed", "tool-eraser", "tool-frame", "tool-hand", "tool-highlight", "tool-laser", "tool-line", "tool-media", "tool-note", "tool-pencil", "tool-pointer", "tool-text", "trash", "triangle-down", "triangle-up", "twitter", "undo", "ungroup", "unlock-small", "unlock", "vertical-align-center", "vertical-align-end", "vertical-align-start", "visible", "warning-triangle", "zoom-in", "zoom-out"];
// @public (undocumented) // @public (undocumented)
export const ToastsContext: Context<ToastsContextType>; export const ToastsContext: Context<ToastsContextType>;

View file

@ -181,6 +181,7 @@ const DebugMenuContent = track(function DebugMenuContent({
<DropdownMenu.Group> <DropdownMenu.Group>
<Toggle label="Read-only" value={app.isReadOnly} onChange={(r) => app.setReadOnly(r)} /> <Toggle label="Read-only" value={app.isReadOnly} onChange={(r) => app.setReadOnly(r)} />
<DebugFlagToggle flag={debugFlags.debugSvg} /> <DebugFlagToggle flag={debugFlags.debugSvg} />
<DebugFlagToggle flag={debugFlags.forceSrgb} />
<DebugFlagToggle <DebugFlagToggle
flag={debugFlags.debugCursors} flag={debugFlags.debugCursors}
onChange={(enabled) => { onChange={(enabled) => {

View file

@ -1,4 +1,5 @@
import { App, featureFlags, useApp } from '@tldraw/editor' import { App, featureFlags, useApp } from '@tldraw/editor'
import { compact } from '@tldraw/utils'
import React from 'react' import React from 'react'
import { useValue } from 'signia-react' import { useValue } from 'signia-react'
import { ToolItem, ToolsContextType, useTools } from './useTools' import { ToolItem, ToolsContextType, useTools } from './useTools'
@ -45,7 +46,7 @@ export function ToolbarSchemaProvider({ overrides, children }: ToolbarSchemaProv
const highlighterEnabled = useValue(featureFlags.highlighterTool) const highlighterEnabled = useValue(featureFlags.highlighterTool)
const toolbarSchema = React.useMemo<ToolbarSchemaContextType>(() => { const toolbarSchema = React.useMemo<ToolbarSchemaContextType>(() => {
const schema: ToolbarSchemaContextType = [ const schema: ToolbarSchemaContextType = compact([
toolbarItem(tools.select), toolbarItem(tools.select),
toolbarItem(tools.hand), toolbarItem(tools.hand),
toolbarItem(tools.draw), toolbarItem(tools.draw),
@ -62,23 +63,20 @@ export function ToolbarSchemaProvider({ overrides, children }: ToolbarSchemaProv
toolbarItem(tools['rhombus']), toolbarItem(tools['rhombus']),
toolbarItem(tools['pentagon']), toolbarItem(tools['pentagon']),
toolbarItem(tools['hexagon']), toolbarItem(tools['hexagon']),
toolbarItem(tools['octagon']), // toolbarItem(tools['octagon']),
toolbarItem(tools['star']), toolbarItem(tools['star']),
toolbarItem(tools['oval']), toolbarItem(tools['oval']),
toolbarItem(tools.line), toolbarItem(tools['x-box']),
toolbarItem(tools['arrow-right']), toolbarItem(tools['check-box']),
toolbarItem(tools['arrow-left']), toolbarItem(tools['arrow-left']),
toolbarItem(tools['arrow-up']), toolbarItem(tools['arrow-up']),
toolbarItem(tools['arrow-down']), toolbarItem(tools['arrow-down']),
toolbarItem(tools['x-box']), toolbarItem(tools['arrow-right']),
toolbarItem(tools['check-box']),
toolbarItem(tools.frame), toolbarItem(tools.frame),
toolbarItem(tools.line),
highlighterEnabled ? toolbarItem(tools.highlight) : null,
toolbarItem(tools.laser), toolbarItem(tools.laser),
] ])
if (highlighterEnabled) {
schema.push(toolbarItem(tools.highlight))
}
if (overrides) { if (overrides) {
return overrides(app, schema, { tools }) return overrides(app, schema, { tools })

View file

@ -210,7 +210,7 @@ export function ToolsProvider({ overrides, children }: ToolsProviderProps) {
readonlyOk: true, readonlyOk: true,
icon: 'tool-highlight', icon: 'tool-highlight',
// TODO: pick a better shortcut // TODO: pick a better shortcut
kbd: 'i', kbd: '!d',
onSelect(source) { onSelect(source) {
app.setSelectedTool('highlight') app.setSelectedTool('highlight')
trackEvent('select-tool', { source, id: 'highlight' }) trackEvent('select-tool', { source, id: 'highlight' })

View file

@ -142,7 +142,6 @@ export type TLUiIconType =
| 'tool-frame' | 'tool-frame'
| 'tool-hand' | 'tool-hand'
| 'tool-highlight' | 'tool-highlight'
| 'tool-highlighter'
| 'tool-laser' | 'tool-laser'
| 'tool-line' | 'tool-line'
| 'tool-media' | 'tool-media'
@ -307,7 +306,6 @@ export const TLUiIconTypes = [
'tool-frame', 'tool-frame',
'tool-hand', 'tool-hand',
'tool-highlight', 'tool-highlight',
'tool-highlighter',
'tool-laser', 'tool-laser',
'tool-line', 'tool-line',
'tool-media', 'tool-media',