1
1
mirror of https://github.com/varkor/quiver.git synced 2024-08-16 01:00:46 +03:00
This commit is contained in:
varkor 2020-12-19 01:18:19 +00:00
parent d6ff7ea022
commit b61be9b9a9
7 changed files with 103 additions and 12 deletions

View File

@ -90,6 +90,8 @@ const CONSTANTS = {
HOOK_BOTTOM: ["hook-bottom"],
/// The corner of a square, used for pullbacks and pushouts.
CORNER: ["corner"],
/// The corner of a square, used for an alternate style for pullbacks and pushouts.
CORNER_INVERSE: ["corner-inverse"],
},
/// The various label alignment options.
LABEL_ALIGNMENT: new Enum(
@ -1073,6 +1075,8 @@ class Arrow {
switch (heads[i]) {
case "epi":
case "corner":
case "corner-inverse":
// FIXME: corner-inverse.
[margin_left, margin_right, margin_begin] = [0, head_width, 0];
break;
case "mono":
@ -1158,11 +1162,14 @@ class Arrow {
// The corner symbol used for pullbacks and pushouts.
case "corner":
case "corner-inverse":
const is_inverse = head_style.endsWith("-inverse");
const LENGTH = 12;
const base_2 = LENGTH / (2 ** 0.5);
const base_point
= bezier.point(t_after_length(arclen_to_head + base_2 * start_sign))
.add(offset);
= bezier.point(t_after_length(
arclen_to_head + (is_inverse ? 0 : base_2 * start_sign)
)).add(offset);
// Draw the two halves of the head.
for (const side_sign of [-1, 1]) {
@ -1173,7 +1180,8 @@ class Arrow {
const PI_4 = Math.PI / 4;
const direction = this.target.origin.sub(this.source.origin).angle();
const corner_angle
= Math.PI + PI_4 * Math.round(4 * direction / Math.PI) - direction;
= (is_inverse ? 0 : Math.PI)
+ PI_4 * Math.round(4 * direction / Math.PI) - direction;
path.line_by(Point.lendir(
LENGTH,

View File

@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" version="1.1" viewBox="0 0 64 64"><style>.st0{fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10}</style><circle cx="52" cy="12.2" r="3"/><circle cx="12" cy="12.2" r="3"/><path d="M18.8 12.2h26.5m-.1 0c-1.9 0-3.5-1.6-3.5-3.5m.1 7c0-1.9 1.6-3.5 3.5-3.5" class="st0"/><circle cx="12" cy="52.2" r="3"/><path d="M12 18.9v26.5m0 0c0-1.9 1.6-3.5 3.5-3.5m-7 0c1.9 0 3.5 1.6 3.5 3.5" class="st0"/><circle cx="52" cy="52.2" r="3"/><path d="M18.8 52.2h26.5m-.1 0c-1.9 0-3.5-1.6-3.5-3.5m.1 7c0-1.9 1.6-3.5 3.5-3.5M52 18.9v26.5m0 0c0-1.9 1.6-3.5 3.5-3.5m-7 0c1.9 0 3.5 1.6 3.5 3.5M24.5 18.5h-6m0 6v-6" class="st0"/></svg>

After

Width:  |  Height:  |  Size: 675 B

View File

@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" version="1.1" viewBox="0 0 64 64"><style>.st0{fill:gray}.st1{fill:none;stroke:gray;stroke-linecap:round;stroke-miterlimit:10}</style><circle cx="52" cy="12.2" r="3" class="st0"/><circle cx="12" cy="12.2" r="3" class="st0"/><path d="M18.8 12.2h26.5m-.1 0c-1.9 0-3.5-1.6-3.5-3.5m.1 7c0-1.9 1.6-3.5 3.5-3.5" class="st1"/><circle cx="12" cy="52.2" r="3" class="st0"/><path d="M12 18.9v26.5m0 0c0-1.9 1.6-3.5 3.5-3.5m-7 0c1.9 0 3.5 1.6 3.5 3.5" class="st1"/><circle cx="52" cy="52.2" r="3" class="st0"/><path d="M18.8 52.2h26.5m-.1 0c-1.9 0-3.5-1.6-3.5-3.5m.1 7c0-1.9 1.6-3.5 3.5-3.5M52 18.9v26.5m0 0c0-1.9 1.6-3.5 3.5-3.5m-7 0c1.9 0 3.5 1.6 3.5 3.5M24.5 18.5h-6m0 6v-6" class="st1"/></svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@ -587,6 +587,10 @@ input[type="text"].flash {
border-color: var(--ui-focus);
}
.panel input.hidden {
display: none;
}
.panel .vertical {
display: inline-block;
width: calc(100% - (20% + 4px) * 2);

View File

@ -526,6 +526,7 @@ QuiverExport.tikz_cd = new class extends QuiverExport {
case "adjunction":
case "corner":
case "corner-inverse":
parameters.push("phantom");
let angle;
@ -537,7 +538,9 @@ QuiverExport.tikz_cd = new class extends QuiverExport {
angle = -Math.round(edge.angle() * 180 / Math.PI);
break;
case "corner":
label = "\"\\lrcorner\"";
case "corner-inverse":
label = edge.options.style.name.endsWith("-inverse") ?
"\"\\ulcorner\"" : "\"\\lrcorner\"";
label_parameters.push("very near start");
// Round the angle to the nearest 45º, so that the corner always
// appears aligned with horizontal, vertical or diagonal lines.
@ -545,7 +548,9 @@ QuiverExport.tikz_cd = new class extends QuiverExport {
break;
}
label_parameters.push(`rotate=${angle}`);
if (angle !== 0) {
label_parameters.push(`rotate=${angle}`);
}
// We allow these sorts of edges to have labels attached,
// even though it's a little unusual.

View File

@ -4,16 +4,18 @@
% This package is currently a wrapper around the `tikz-cd` package, importing necessary TikZ
% libraries, and defining a new TikZ style for curves of a fixed height.
%
% Version: 1.0.1
% Version: 1.1.0
% Authors:
% - varkor (https://github.com/varkor)
% - AndréC (https://tex.stackexchange.com/users/138900/andr%C3%A9c)
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{quiver}[2020/11/27 quiver]
\ProvidesPackage{quiver}[2020/12/18 quiver]
% `tikz-cd` is necessary to draw commutative diagrams.
\RequirePackage{tikz-cd}
% `amssymb` is necessary for `\lrcorner` and `\ulcorner`.
\RequirePackage{amssymb}
% `calc` is necessary to draw curved arrows.
\usetikzlibrary{calc}
% `pathmorphing` is necessary to draw squiggly arrows.

View File

@ -2819,6 +2819,13 @@ class UI {
style.heads = CONSTANTS.ARROW_HEAD_STYLE.NONE;
style.tails = CONSTANTS.ARROW_HEAD_STYLE.CORNER;
break;
// Pullback/pushout corner.
case "corner-inverse":
style.body_style = CONSTANTS.ARROW_BODY_STYLE.NONE;
style.heads = CONSTANTS.ARROW_HEAD_STYLE.NONE;
style.tails = CONSTANTS.ARROW_HEAD_STYLE.CORNER_INVERSE;
break;
}
return style;
@ -2847,7 +2854,7 @@ class UI {
const value = parseInt(slider.element.value);
const step = parseInt(slider.element.step);
slider.element.value = value + step * delta;
slider.element.dispatchEvent(new Event("input"));
slider.dispatch(new Event("input"));
}
return focused_sliders.length > 0;
}
@ -3810,6 +3817,7 @@ class Panel {
["arrow", "Arrow", Edge.default_options().style, "a"],
["adjunction", "Adjunction", { name: "adjunction" }, "j"],
["corner", "Pullback / pushout", { name: "corner" }, "p"],
["corner-inverse", "Pullback / pushout", { name: "corner-inverse" }, "p"],
],
"edge-type",
["large"],
@ -3856,7 +3864,7 @@ class Panel {
// we get the expected style, rather than the default style.
if (data.name === "arrow") {
ui.element.query_selector_all('.arrow-style input[type="radio"]:checked')
.forEach((input) => input.element.dispatchEvent(new Event("change")))
.forEach((input) => input.dispatch(new Event("change")))
} else {
this.defocus_inputs();
}
@ -3868,6 +3876,40 @@ class Panel {
}),
);
// TODO: history
// TODO: save in localStorage
// TODO: fix pressing P for the first time
// TODO: address FIXMEs
const corner_button = this.element
.query_selector(`input[name="edge-type"][value="corner"]`);
const corner_inverse_button = this.element
.query_selector(`input[name="edge-type"][value="corner-inverse"]`);
corner_inverse_button.class_list.add("hidden");
// When the user clicks on the corner button, it alternates between `corner` and
// `corner-inverse`.
const alternate_buttons = [corner_button, corner_inverse_button];
for (let i = 0; i < alternate_buttons.length; ++i) {
const button = alternate_buttons[i];
const next_button = alternate_buttons[(i + 1) % alternate_buttons.length];
button.listen("mouseup", () => {
if (button.element.checked) {
button.element.disabled = true;
next_button.element.disabled = false;
next_button.class_list.remove("hidden");
button.class_list.add("hidden");
next_button.element.checked = true;
next_button.dispatch(new Event("change"));
UI.delay(() => button.element.disabled = false);
}
});
button.listen("change", () => {
next_button.class_list.add("hidden");
button.class_list.remove("hidden");
});
}
progress_style_selection = () => {
const elements = [head_styles, body_styles, tail_styles];
while (elements.length > 0) {
@ -4179,10 +4221,12 @@ class Panel {
let { length, options, draw_label } = properties(data);
// We use a custom pre-drawn SVG for the pullback/pushout button.
if (options.style.name === "corner") {
if (options.style.name.startsWith("corner")) {
button.set_style({
"background-image": ["", "un"].map((prefix) => {
return `url("icons/pullback-${prefix}checked.svg")`;
return `url("icons/${
options.style.name.endsWith("inverse") ? "var-" : ""
}pullback-${prefix}checked.svg")`;
}).join(", ")
});
return button;
@ -4257,7 +4301,7 @@ class Panel {
event.triggered_by_shortcut = true;
event.idempotent = option.element.checked;
option.element.checked = true;
option.element.dispatchEvent(event);
option.dispatch(event);
Shortcuts.flash(option);
// Prevent other elements from being triggered by the same key
// press.
@ -4382,6 +4426,8 @@ class Panel {
}
};
let [corners, inverse_corners] = [0, 0];
// Collect the consistent and varying input values.
for (const cell of ui.selection) {
// Options applying to all cells.
@ -4426,6 +4472,12 @@ class Panel {
}
} else {
all_edges_are_arrows = false;
if (cell.options.style.name === "corner") {
++corners;
}
if (cell.options.style.name === "corner-inverse") {
++inverse_corners;
}
}
}
}
@ -4491,6 +4543,22 @@ class Panel {
get_input("body-type", "solid").element.checked = true;
get_input("head-type", "arrowhead").element.checked = true;
}
// Display the relevant pullback/pushout button.
const corner_button = this.element
.query_selector(`input[name="edge-type"][value="corner"]`);
const corner_inverse_button = this.element
.query_selector(`input[name="edge-type"][value="corner-inverse"]`);
if (corners > inverse_corners && corners > 0) {
corner_button.class_list.remove("hidden");
corner_inverse_button.class_list.add("hidden");
} else if (inverse_corners > corners && inverse_corners > 0) {
corner_inverse_button.class_list.remove("hidden");
corner_button.class_list.add("hidden");
} else {
// Pick the user's preference.
// FIXME
}
// Update the actual `value` attribute for the offset, curve, length, and level sliders
// so that we can reference it in the CSS.