Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ pub enum FrontendMessage {
spacing: f64,
interval: f64,
visible: bool,
tilt: f64,
},
UpdateDocumentScrollbars {
position: (f64, f64),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
spacing: ruler_spacing,
interval: ruler_interval,
visible: self.rulers_visible,
tilt: if self.graph_view_overlay_open { 0. } else { current_ptz.tilt() },
});
}
DocumentMessage::RenderScrollbars => {
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::RequestStatusBarInfoLayout => {
#[cfg(not(target_family = "wasm"))]
let widgets = vec![TextLabel::new("Graphite 1.0.0-RC3").disabled(true).widget_instance()]; // TODO: After the RCs, call this "Graphite (beta) x.y.z"
let widgets = vec![TextLabel::new("Graphite 1.0.0-RC4").disabled(true).widget_instance()]; // TODO: After the RCs, call this "Graphite (beta) x.y.z"
#[cfg(target_family = "wasm")]
let widgets = vec![];

Expand Down
28 changes: 23 additions & 5 deletions frontend/src/components/panels/Document.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
let rulerSpacing = 100;
let rulerInterval = 100;
let rulersVisible = true;
let rulerTilt = 0;

// Rendered SVG viewport data
let artworkSvg = "";
Expand Down Expand Up @@ -284,11 +285,12 @@
scrollbarMultiplier = { x: multiplier[0], y: multiplier[1] };
}

export function updateDocumentRulers(origin: [number, number], spacing: number, interval: number, visible: boolean) {
export function updateDocumentRulers(origin: [number, number], spacing: number, interval: number, visible: boolean, tilt: number) {
rulerOrigin = { x: origin[0], y: origin[1] };
rulerSpacing = spacing;
rulerInterval = interval;
rulersVisible = visible;
rulerTilt = tilt;
}

// Update mouse cursor icon
Expand Down Expand Up @@ -483,8 +485,8 @@
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => {
await tick();

const { origin, spacing, interval, visible } = data;
updateDocumentRulers(origin, spacing, interval, visible);
const { origin, spacing, interval, visible, tilt } = data;
updateDocumentRulers(origin, spacing, interval, visible, tilt);
});

// Update mouse cursor icon
Expand Down Expand Up @@ -575,13 +577,29 @@
{#if rulersVisible}
<LayoutRow class="ruler-or-scrollbar top-ruler">
<LayoutCol class="ruler-corner"></LayoutCol>
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
<RulerInput
originX={rulerOrigin.x}
originY={rulerOrigin.y}
majorMarkSpacing={rulerSpacing}
numberInterval={rulerInterval}
direction="Horizontal"
tilt={rulerTilt}
bind:this={rulerHorizontal}
/>
</LayoutRow>
{/if}
<LayoutRow class="viewport-container-inner-1">
{#if rulersVisible}
<LayoutCol class="ruler-or-scrollbar">
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
<RulerInput
originX={rulerOrigin.x}
originY={rulerOrigin.y}
majorMarkSpacing={rulerSpacing}
numberInterval={rulerInterval}
direction="Vertical"
tilt={rulerTilt}
bind:this={rulerVertical}
/>
</LayoutCol>
{/if}
<LayoutCol class="viewport-container-inner-2" styles={{ cursor: canvasCursor }} data-viewport-container>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/panels/Welcome.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<LayoutCol class="bottom-message">
<TextLabel italic={true} disabled={true}>
{#if isPlatformNative()}
You are testing Release Candidate 3 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter.
You are testing Release Candidate 4 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter.
{/if}
</TextLabel>
</LayoutCol>
Expand Down
120 changes: 87 additions & 33 deletions frontend/src/components/widgets/inputs/RulerInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,139 @@
const MAJOR_MARK_THICKNESS = 16;
const MINOR_MARK_THICKNESS = 6;
const MICRO_MARK_THICKNESS = 3;
const TAU = 2 * Math.PI;

type RulerDirection = "Horizontal" | "Vertical";

export let direction: RulerDirection = "Vertical";
export let origin: number;
export let originX: number;
export let originY: number;
export let numberInterval: number;
export let majorMarkSpacing: number;
export let minorDivisions = 5;
export let microDivisions = 2;
export let tilt: number = 0;

let rulerInput: HTMLDivElement | undefined;
let rulerLength = 0;
let svgBounds = { width: "0px", height: "0px" };

$: svgPath = computeSvgPath(direction, origin, majorMarkSpacing, minorDivisions, microDivisions, rulerLength);
$: svgTexts = computeSvgTexts(direction, origin, majorMarkSpacing, numberInterval, rulerLength);

function computeSvgPath(direction: RulerDirection, origin: number, majorMarkSpacing: number, minorDivisions: number, microDivisions: number, rulerLength: number): string {
const isVertical = direction === "Vertical";
const lineDirection = isVertical ? "H" : "V";

const offsetStart = mod(origin, majorMarkSpacing);
const shiftedOffsetStart = offsetStart - majorMarkSpacing;
type Axis = { sign: number; vec: [number, number] };

$: axes = computeAxes(tilt);
$: isHorizontal = direction === "Horizontal";
$: trackedAxis = isHorizontal ? axes.horiz : axes.vert;
$: otherAxis = isHorizontal ? axes.vert : axes.horiz;
$: stretchFactor = 1 / (isHorizontal ? trackedAxis.vec[0] : trackedAxis.vec[1]);
$: stretchedSpacing = majorMarkSpacing * stretchFactor;
$: effectiveOrigin = computeEffectiveOrigin(direction, originX, originY, otherAxis);
$: svgPath = computeSvgPath(direction, effectiveOrigin, stretchedSpacing, stretchFactor, minorDivisions, microDivisions, rulerLength, otherAxis);
$: svgTexts = computeSvgTexts(direction, effectiveOrigin, stretchedSpacing, numberInterval, rulerLength, trackedAxis, otherAxis);

function computeAxes(tilt: number): { horiz: Axis; vert: Axis } {
const normTilt = ((tilt % TAU) + TAU) % TAU;
const octant = Math.floor((normTilt + Math.PI / 4) / (Math.PI / 2)) % 4;

const [c, s] = [Math.cos(tilt), Math.sin(tilt)];
const posX: Axis = { sign: 1, vec: [c, s] };
const posY: Axis = { sign: 1, vec: [-s, c] };
const negX: Axis = { sign: -1, vec: [-c, -s] };
const negY: Axis = { sign: -1, vec: [s, -c] };

if (octant === 0) return { horiz: posX, vert: posY };
if (octant === 1) return { horiz: negY, vert: posX };
if (octant === 2) return { horiz: negX, vert: negY };
return { horiz: posY, vert: negX };
}

const divisions = majorMarkSpacing / minorDivisions / microDivisions;
const majorMarksFrequency = minorDivisions * microDivisions;
function computeEffectiveOrigin(direction: RulerDirection, ox: number, oy: number, otherAxis: Axis): number {
const [vx, vy] = otherAxis.vec;
return direction === "Horizontal" ? ox - oy * (vx / vy) : oy - ox * (vy / vx);
}

let dPathAttribute = "";
function computeSvgPath(
direction: RulerDirection,
effectiveOrigin: number,
stretchedSpacing: number,
stretchFactor: number,
minorDivisions: number,
microDivisions: number,
rulerLength: number,
otherAxis: Axis,
): string {
const adaptive = stretchFactor > 1.3 ? { minor: minorDivisions, micro: 1 } : { minor: minorDivisions, micro: microDivisions };
const divisions = stretchedSpacing / adaptive.minor / adaptive.micro;
const majorMarksFrequency = adaptive.minor * adaptive.micro;
const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing;

const [vx, vy] = otherAxis.vec;
const flip = direction === "Horizontal" ? (vy > 0 ? -1 : 1) : vx > 0 ? -1 : 1;
const [dx, dy] = [vx * flip, vy * flip];
const [sxBase, syBase] = direction === "Horizontal" ? [0, RULER_THICKNESS] : [RULER_THICKNESS, 0];

let path = "";
let i = 0;
for (let location = shiftedOffsetStart; location < rulerLength; location += divisions) {
for (let location = shiftedOffsetStart; location < rulerLength + RULER_THICKNESS; location += divisions) {
let length;
if (i % majorMarksFrequency === 0) length = MAJOR_MARK_THICKNESS;
else if (i % microDivisions === 0) length = MINOR_MARK_THICKNESS;
else if (i % adaptive.micro === 0) length = MINOR_MARK_THICKNESS;
else length = MICRO_MARK_THICKNESS;
i += 1;

const destination = Math.round(location) + 0.5;
const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;
dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;
const [sx, sy] = direction === "Horizontal" ? [destination, syBase] : [sxBase, destination];
path += `M${sx},${sy}l${dx * length},${dy * length} `;
}

return dPathAttribute;
return path;
}

function computeSvgTexts(direction: RulerDirection, origin: number, majorMarkSpacing: number, numberInterval: number, rulerLength: number): { transform: string; text: string }[] {
function computeSvgTexts(
direction: RulerDirection,
effectiveOrigin: number,
stretchedSpacing: number,
numberInterval: number,
rulerLength: number,
trackedAxis: Axis,
otherAxis: Axis,
): { transform: string; text: string }[] {
const isVertical = direction === "Vertical";

const offsetStart = mod(origin, majorMarkSpacing);
const shiftedOffsetStart = offsetStart - majorMarkSpacing;
const [vx, vy] = otherAxis.vec;
const flip = isVertical ? (vx > 0 ? -1 : 1) : vy > 0 ? -1 : 1;
const tipOffsetX = vx * flip * MAJOR_MARK_THICKNESS;
const tipOffsetY = vy * flip * MAJOR_MARK_THICKNESS;

const svgTextCoordinates = [];
const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing;
const increments = Math.round((shiftedOffsetStart - effectiveOrigin) / stretchedSpacing);
let labelNumber = increments * numberInterval * trackedAxis.sign;

let labelNumber = (Math.ceil(-origin / majorMarkSpacing) - 1) * numberInterval;
const results: { transform: string; text: string }[] = [];

for (let location = shiftedOffsetStart; location < rulerLength; location += majorMarkSpacing) {
for (let location = shiftedOffsetStart; location < rulerLength; location += stretchedSpacing) {
const destination = Math.round(location);
const x = isVertical ? 9 : destination + 2;
const y = isVertical ? destination + 1 : 9;
const x = isVertical ? 9 : destination + 2 + tipOffsetX;
const y = isVertical ? destination + 1 + tipOffsetY : 9;

let transform = `translate(${x} ${y})`;
if (isVertical) transform += " rotate(270)";

const text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, "");

svgTextCoordinates.push({ transform, text });
const num = Math.abs(labelNumber) < 1e-9 ? 0 : labelNumber;
const text = numberInterval >= 1 ? `${num}` : num.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, "");

labelNumber += numberInterval;
results.push({ transform, text });
labelNumber += numberInterval * trackedAxis.sign;
}

return svgTextCoordinates;
return results;
}

export function resize() {
if (!rulerInput) return;

const isVertical = direction === "Vertical";

const newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth;
const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;
const roundedUp = (Math.floor(newLength / stretchedSpacing) + 2) * stretchedSpacing;

if (roundedUp !== rulerLength) {
rulerLength = roundedUp;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/window/MainWindow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
{#if $tooltip.visible}
<Tooltip />
{/if}
{#if isPlatformNative() && new Date() > new Date("2026-03-15")}
{#if isPlatformNative() && new Date() > new Date("2026-04-30")}
<LayoutCol class="release-candidate-expiry">
<TextLabel>
<p>
Expand Down
Loading