Stack branch labels when a row is tight; fan them out on hover or selection
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { MESSAGE_KEEP, PEEK, shouldStack, stackWidth } from "./labels";
|
||||
|
||||
describe("shouldStack", () => {
|
||||
it("never stacks a single label", () => {
|
||||
expect(shouldStack([500], 900, 100)).toBe(false);
|
||||
});
|
||||
it("does not stack when labels and message fit", () => {
|
||||
expect(shouldStack([100, 100], 200, 600)).toBe(false);
|
||||
});
|
||||
it("stacks when the labels would leave the message too little room", () => {
|
||||
expect(shouldStack([200, 200], 900, 300 + MESSAGE_KEEP)).toBe(true);
|
||||
});
|
||||
it("lets a short message leave its room to the labels", () => {
|
||||
expect(shouldStack([200, 200], 50, 470)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("stackWidth", () => {
|
||||
it("is the first label plus a peek for each other label", () => {
|
||||
expect(stackWidth([80, 120, 60])).toBe(80 + 2 * PEEK);
|
||||
expect(stackWidth([])).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/** How far each label behind the first one sticks out of a stack, in pixels. */
|
||||
export const PEEK = 6;
|
||||
export const GAP = 5;
|
||||
/** The message keeps at least this much width before labels start stacking. */
|
||||
export const MESSAGE_KEEP = 240;
|
||||
|
||||
/** Whether a row's labels must stack because they do not fit next to the message. */
|
||||
export function shouldStack(labelWidths: number[], messageWidth: number, space: number): boolean {
|
||||
if (labelWidths.length < 2) return false;
|
||||
const full = labelWidths.reduce((a, b) => a + b, 0) + GAP * (labelWidths.length - 1);
|
||||
// 8px is the gap between the message and the labels.
|
||||
return full + Math.min(messageWidth, MESSAGE_KEEP) + 8 > space;
|
||||
}
|
||||
|
||||
export function stackWidth(labelWidths: number[]): number {
|
||||
return labelWidths.length === 0 ? 0 : labelWidths[0] + PEEK * (labelWidths.length - 1);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
const cache = new Map<string, number>();
|
||||
|
||||
/** Width in pixels of `text` drawn in the CSS `font`. */
|
||||
export function textWidth(text: string, font: string): number {
|
||||
const key = font + "\u0000" + text;
|
||||
let w = cache.get(key);
|
||||
if (w === undefined) {
|
||||
ctx.font = font;
|
||||
w = ctx.measureText(text).width;
|
||||
cache.set(key, w);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
Reference in New Issue
Block a user