$Choose = (things, display, choose, is_chosen, gray_background_for_unselected) => {
	const $chooser = $(E("div")).addClass("chooser").css("touch-action", "none");
	const choose_thing = (thing) => {
		if (is_chosen(thing)) {
			return;
		}
		choose(thing);
		$G.trigger("option-changed");
	};
	$chooser.on("update", () => {
		if (!$chooser.is(":visible")) {
			return;
		}
		$chooser.empty();
		for (let i = 0; i < things.length; i++) {
			((thing) => {
				const $option_container = $(E("div")).addClass("chooser-option").appendTo($chooser);
				// @ts-ignore
				$option_container.data("thing", thing);
				/**
				 * @param {number} width
				 * @param {number} height
				 * @returns {PixelCanvas}
				 */
				const reuse_canvas = (width, height) => {
					let option_canvas = /** @type {PixelCanvas | undefined} */ ($option_container.find("canvas")[0]);
					if (option_canvas) {
						if (option_canvas.width !== width) { option_canvas.width = width; }
						if (option_canvas.height !== height) { option_canvas.height = height; }
					} else {
						option_canvas = make_canvas(width, height);
						$option_container.append(option_canvas);
					}
					return option_canvas;
				};
				const reuse_div = (width, height) => {
					let option_div = $option_container.find("div")[0];
					if (option_div) {
						if (option_div.style.width !== width + "px") { option_div.style.width = width + "px"; }
						if (option_div.style.height !== height + "px") { option_div.style.height = height + "px"; }
					} else {
						option_div = E("div");
						option_div.style.width = width + "px";
						option_div.style.height = height + "px";
						$option_container.append(option_div);
					}
					return option_div;
				};
				const update = () => {
					const selected_color = getComputedStyle($chooser[0]).getPropertyValue("--Hilight");
					const unselected_color = gray_background_for_unselected ? "rgb(192, 192, 192)" : "";
					$option_container.css({
						backgroundColor: is_chosen(thing) ? selected_color : unselected_color,
					});
					display(thing, is_chosen(thing), reuse_canvas, reuse_div);
				};
				update();
				$G.on("option-changed theme-load redraw-tool-options-because-webglcontextrestored", update);
			})(things[i]);
		}

		const onpointerover_while_pointer_down = (event) => {
			const option_container = event.target.closest(".chooser-option");
			if (option_container) {
				choose_thing($(option_container).data("thing"));
			}
		};
		const ontouchmove_while_pointer_down = (event) => {
			const touch = event.originalEvent.changedTouches[0];
			const target = document.elementFromPoint(touch.clientX, touch.clientY);
			const option_container = target.closest(".chooser-option");
			if (option_container) {
				choose_thing($(option_container).data("thing"));
			}
			event.preventDefault();
		};
		$chooser.on("pointerdown click", (event) => {
			const option_container = event.target.closest(".chooser-option");
			if (option_container) {
				choose_thing($(option_container).data("thing"));
			}
			if (event.type === "pointerdown") {
				// glide thru tool options
				$chooser.on("pointerover", onpointerover_while_pointer_down);
				$chooser.on("touchmove", ontouchmove_while_pointer_down);
			}
		});
		$G.on("pointerup pointercancel", () => {
			$chooser.off("pointerover", onpointerover_while_pointer_down);
			$chooser.off("touchmove", ontouchmove_while_pointer_down);
		});
	});
	return $chooser;
}
@template T
@param {T[]} things
@param {(thing: T, is_chosen: boolean, reuse_canvas: (width: number, height: number) => PixelCanvas, reuse_div: (width: number, height: number) => HTMLDivElement) => HTMLCanvasElement | HTMLDivElement} display
@param {(thing: T) => void} choose
@param {(thing: T) => boolean} is_chosen
@param {boolean=} gray_background_for_unselected
@returns {JQuery<HTMLDivElement> & { stroke?: boolean, fill?: boolean }}