mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Improve tooltip docs with Markdown styling and refined math node explanations (#3488)
This commit is contained in:
parent
2c21e1a90b
commit
f1e8ebefc5
19 changed files with 276 additions and 185 deletions
|
|
@ -70,6 +70,9 @@ pub enum FrontendMessage {
|
|||
SendShortcutAltClick {
|
||||
shortcut: Option<ActionShortcut>,
|
||||
},
|
||||
SendShortcutShiftClick {
|
||||
shortcut: Option<ActionShortcut>,
|
||||
},
|
||||
|
||||
// Trigger prefix: cause a frontend specific API to do something
|
||||
TriggerAboutGraphiteLocalizedCommitDate {
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Merges new content as an entry into the graphic table that represents a layer compositing stack."),
|
||||
description: Cow::Borrowed("Merges the provided content as a new element in the graphic table that represents a layer compositing stack."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
responses.add(FrontendMessage::SendShortcutAltClick {
|
||||
shortcut: action_shortcut_manual!(Key::Alt, Key::MouseLeft),
|
||||
});
|
||||
responses.add(FrontendMessage::SendShortcutShiftClick {
|
||||
shortcut: action_shortcut_manual!(Key::Shift, Key::MouseLeft),
|
||||
});
|
||||
|
||||
// Before loading any documents, initially prepare the welcome screen buttons layout
|
||||
responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ pub struct PivotGizmoState {
|
|||
|
||||
impl PivotGizmoState {
|
||||
pub fn is_pivot_type(&self) -> bool {
|
||||
// A disabled pivot is considered a pivot-type gizmo that is always centered
|
||||
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
// State provider systems
|
||||
let dialog = createDialogState(editor);
|
||||
setContext("dialog", dialog);
|
||||
let tooltip = createTooltipState();
|
||||
let tooltip = createTooltipState(editor);
|
||||
setContext("tooltip", tooltip);
|
||||
let document = createDocumentState(editor);
|
||||
setContext("document", document);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy, createEventDispatcher } from "svelte";
|
||||
import { getContext, onDestroy, createEventDispatcher } from "svelte";
|
||||
|
||||
import type { HSV, RGB, FillChoice } from "@graphite/messages";
|
||||
import type { MenuDirection } from "@graphite/messages";
|
||||
import type { HSV, RGB, FillChoice, MenuDirection } from "@graphite/messages";
|
||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
import { clamp } from "@graphite/utility-functions/math";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
];
|
||||
|
||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice; startHistoryTransaction: undefined }>();
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
|
||||
export let colorOrGradient: FillChoice;
|
||||
export let allowNone = false;
|
||||
|
|
@ -424,12 +425,16 @@
|
|||
"--opaque-color-contrasting": (newColor.opaque() || new Color(0, 0, 0, 1)).contrastingColor(),
|
||||
}}
|
||||
>
|
||||
{@const hueDescription = "The shade along the spectrum of the rainbow."}
|
||||
{@const saturationDescription = "The vividness from grayscale to full color."}
|
||||
{@const valueDescription = "The brightness from black to full color."}
|
||||
<LayoutCol class="pickers-and-gradient">
|
||||
<LayoutRow class="pickers">
|
||||
<LayoutCol
|
||||
class="saturation-value-picker"
|
||||
data-tooltip-label="Saturation and Value"
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
data-tooltip-description={`To move only along the saturation (X) or value (Y) axis, perform the shortcut shown.${disabled ? "\n\nDisabled (read-only)." : ""}`}
|
||||
data-tooltip-shortcut={$tooltip.shiftClickShortcut?.shortcut ? JSON.stringify($tooltip.shiftClickShortcut.shortcut) : undefined}
|
||||
on:pointerdown={onPointerDown}
|
||||
data-saturation-value-picker
|
||||
>
|
||||
|
|
@ -449,7 +454,7 @@
|
|||
<LayoutCol
|
||||
class="hue-picker"
|
||||
data-tooltip-label="Hue"
|
||||
data-tooltip-description={`The shade along the spectrum of the rainbow.${disabled ? "\n\nDisabled (read-only)." : ""}`}
|
||||
data-tooltip-description={`${hueDescription}${disabled ? "\n\nDisabled (read-only)." : ""}`}
|
||||
on:pointerdown={onPointerDown}
|
||||
data-hue-picker
|
||||
>
|
||||
|
|
@ -522,10 +527,8 @@
|
|||
</LayoutRow>
|
||||
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltipDescription="Color model, color space, and HDR (coming soon)." /> -->
|
||||
<LayoutRow>
|
||||
<TextLabel
|
||||
tooltipLabel="Hex Color Code"
|
||||
tooltipDescription="Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors.">Hex</TextLabel
|
||||
>
|
||||
{@const hexDescription = "Color code in hexadecimal format. 6 digits if opaque, 8 with alpha. Accepts input of CSS color values including named colors."}
|
||||
<TextLabel tooltipLabel="Hex Color Code" tooltipDescription={hexDescription}>Hex</TextLabel>
|
||||
<Separator type="Related" />
|
||||
<LayoutRow>
|
||||
<TextInput
|
||||
|
|
@ -537,7 +540,7 @@
|
|||
}}
|
||||
centered={true}
|
||||
tooltipLabel="Hex Color Code"
|
||||
tooltipDescription="Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."
|
||||
tooltipDescription={hexDescription}
|
||||
bind:this={hexCodeInputWidget}
|
||||
/>
|
||||
</LayoutRow>
|
||||
|
|
@ -601,16 +604,17 @@
|
|||
v: "Value Component",
|
||||
}[channel]}
|
||||
tooltipDescription={{
|
||||
h: "The shade along the spectrum of the rainbow.",
|
||||
s: "The vividness from grayscale to full color.",
|
||||
v: "The brightness from black to full color.",
|
||||
h: hueDescription,
|
||||
s: saturationDescription,
|
||||
v: valueDescription,
|
||||
}[channel]}
|
||||
/>
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
</LayoutRow>
|
||||
<LayoutRow>
|
||||
<TextLabel tooltipLabel="Alpha" tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%).">Alpha</TextLabel>
|
||||
{@const alphaDescription = "The level of translucency, from transparent (0%) to opaque (100%)."}
|
||||
<TextLabel tooltipLabel="Alpha" tooltipDescription={alphaDescription}>Alpha</TextLabel>
|
||||
<Separator type="Related" />
|
||||
<NumberInput
|
||||
value={!isNone ? alpha * 100 : undefined}
|
||||
|
|
@ -630,7 +634,7 @@
|
|||
mode="Range"
|
||||
displayDecimalPlaces={1}
|
||||
tooltipLabel="Alpha"
|
||||
tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%)."
|
||||
tooltipDescription={alphaDescription}
|
||||
/>
|
||||
</LayoutRow>
|
||||
<LayoutRow class="leftover-space" />
|
||||
|
|
@ -670,7 +674,7 @@
|
|||
data-pure-tile={name.toLowerCase()}
|
||||
style:--pure-color={color}
|
||||
style:--pure-color-gray={gray}
|
||||
data-tooltip-label="Set to Red"
|
||||
data-tooltip-label={`Set to ${name}`}
|
||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||
/>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
let self: FloatingMenu | undefined;
|
||||
|
||||
$: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim());
|
||||
$: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim());
|
||||
$: label = parseMarkdown(filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim()));
|
||||
$: description = parseMarkdown(filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim()));
|
||||
$: shortcutJSON = $tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim();
|
||||
$: shortcut = ((shortcutJSON) => {
|
||||
if (!shortcutJSON) return undefined;
|
||||
|
|
@ -32,6 +32,26 @@
|
|||
if (text?.trim().toUpperCase() === "TODO" && !editor.handle.inDevelopmentMode()) return "";
|
||||
return text;
|
||||
}
|
||||
|
||||
function parseMarkdown(markdown: string | undefined): string | undefined {
|
||||
if (!markdown) return undefined;
|
||||
|
||||
return (
|
||||
markdown
|
||||
// .split("\n")
|
||||
// .map((line) => line.trim())
|
||||
// .join("\n")
|
||||
// .split("\n\n")
|
||||
// .map((paragraph) => paragraph.replaceAll("\n", " "))
|
||||
// .join("\n\n")
|
||||
// Bold
|
||||
.replace(/\*\*((?:(?!\*\*).)+)\*\*/g, "<strong>$1</strong>")
|
||||
// Italic
|
||||
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
|
||||
// Backticks
|
||||
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if label || description}
|
||||
|
|
@ -40,7 +60,7 @@
|
|||
{#if label || shortcut}
|
||||
<LayoutRow class="tooltip-header">
|
||||
{#if label}
|
||||
<TextLabel class="tooltip-label">{label}</TextLabel>
|
||||
<TextLabel class="tooltip-label">{@html label}</TextLabel>
|
||||
{/if}
|
||||
{#if shortcut}
|
||||
<ShortcutLabel shortcut={{ shortcut }} />
|
||||
|
|
@ -48,7 +68,7 @@
|
|||
</LayoutRow>
|
||||
{/if}
|
||||
{#if description}
|
||||
<TextLabel class="tooltip-description">{description}</TextLabel>
|
||||
<TextLabel class="tooltip-description">{@html description}</TextLabel>
|
||||
{/if}
|
||||
</FloatingMenu>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@
|
|||
UpdateLayersPanelControlBarLeftLayout,
|
||||
UpdateLayersPanelControlBarRightLayout,
|
||||
UpdateLayersPanelBottomBarLayout,
|
||||
SendShortcutAltClick,
|
||||
} from "@graphite/messages";
|
||||
import type { ActionShortcut, DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
|
||||
import type { DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
|
||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
|
||||
let list: LayoutCol | undefined;
|
||||
|
||||
|
|
@ -73,13 +74,7 @@
|
|||
let layersPanelControlBarRightLayout: Layout = [];
|
||||
let layersPanelBottomBarLayout: Layout = [];
|
||||
|
||||
let altClickShortcut: ActionShortcut | undefined;
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutAltClick, async (data) => {
|
||||
altClickShortcut = data.shortcut;
|
||||
});
|
||||
|
||||
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => {
|
||||
patchLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
|
||||
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
|
||||
|
|
@ -628,7 +623,7 @@
|
|||
? "Hide the layers nested within. (To affect all open descendants, perform the shortcut shown.)"
|
||||
: "Show the layers nested within. (To affect all closed descendants, perform the shortcut shown.)") +
|
||||
(listing.entry.ancestorOfSelected && !listing.entry.expanded ? "\n\nA selected layer is currently contained within.\n" : "")}
|
||||
data-tooltip-shortcut={altClickShortcut?.shortcut ? JSON.stringify(altClickShortcut.shortcut) : undefined}
|
||||
data-tooltip-shortcut={$tooltip.altClickShortcut?.shortcut ? JSON.stringify($tooltip.altClickShortcut.shortcut) : undefined}
|
||||
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
|
||||
tabindex="0"
|
||||
></button>
|
||||
|
|
@ -639,8 +634,9 @@
|
|||
<IconLabel
|
||||
icon="Clipped"
|
||||
class="clipped-arrow"
|
||||
tooltipDescription="Clipping mask is active. To release it, perform the shortcut on the layer border."
|
||||
tooltipShortcut={altClickShortcut}
|
||||
tooltipLabel="Layer Clipped"
|
||||
tooltipDescription="Clipping mask is active. To release it, target the bottom border of the layer and perform the shortcut shown."
|
||||
tooltipShortcut={$tooltip.altClickShortcut}
|
||||
/>
|
||||
{/if}
|
||||
<div class="thumbnail">
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@
|
|||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
style:--layer-area-width={layerAreaWidth}
|
||||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-description={`
|
||||
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
|
||||
`.trim()}
|
||||
|
|
@ -651,7 +651,7 @@
|
|||
style:--clip-path-id={`url(#${clipPathId})`}
|
||||
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
|
||||
data-tooltip-description={`
|
||||
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
|
||||
`.trim()}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,8 @@
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
&.monospace {
|
||||
&.monospace,
|
||||
code {
|
||||
font-family: "Source Code Pro", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -94,5 +95,10 @@
|
|||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--color-3-darkgray);
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
import { SendShortcutF11 } from "@graphite/messages";
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let f11Shortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutF11, async (data) => {
|
||||
f11Shortcut = data.shortcut;
|
||||
});
|
||||
});
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
|
||||
async function handleClick() {
|
||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||
|
|
@ -31,7 +22,7 @@
|
|||
on:click={handleClick}
|
||||
tooltipLabel={$fullscreen.windowFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
tooltipDescription={$fullscreen.keyboardLockApiSupported ? "While fullscreen, keyboard shortcuts normally reserved by the browser become available." : ""}
|
||||
tooltipShortcut={f11Shortcut}
|
||||
tooltipShortcut={$tooltip.f11Shortcut}
|
||||
>
|
||||
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||
</LayoutRow>
|
||||
|
|
|
|||
|
|
@ -481,11 +481,11 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
The browser's clipboard permission has been denied.
|
||||
|
||||
Open the browser's website settings (usually accessible
|
||||
just left of the URL) to allow this permission.
|
||||
just left of the URL bar) to allow this permission.
|
||||
`;
|
||||
const nothing = stripIndents`
|
||||
No valid clipboard data was found. You may have better
|
||||
luck pasting with the standard keyboard shortcut instead.
|
||||
success pasting with the standard keyboard shortcut instead.
|
||||
`;
|
||||
|
||||
const matchMessage = {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,11 @@ export class SendShortcutAltClick extends JsMessage {
|
|||
readonly shortcut!: ActionShortcut | undefined;
|
||||
}
|
||||
|
||||
export class SendShortcutShiftClick extends JsMessage {
|
||||
@Transform(({ value }: { value: ActionShortcut }) => value || undefined)
|
||||
readonly shortcut!: ActionShortcut | undefined;
|
||||
}
|
||||
|
||||
export class UpdateNodeThumbnail extends JsMessage {
|
||||
readonly id!: bigint;
|
||||
|
||||
|
|
@ -1696,6 +1701,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
SendUIMetadata,
|
||||
SendShortcutF11,
|
||||
SendShortcutAltClick,
|
||||
SendShortcutShiftClick,
|
||||
TriggerAboutGraphiteLocalizedCommitDate,
|
||||
TriggerDisplayThirdPartyLicensesDialog,
|
||||
TriggerExportImage,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function createAppWindowState(editor: Editor) {
|
|||
maximized: false,
|
||||
fullscreen: false,
|
||||
viewportHolePunch: false,
|
||||
uiScale: 1.0,
|
||||
uiScale: 1,
|
||||
});
|
||||
|
||||
// Set up message subscriptions on creation
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import { SendShortcutAltClick, SendShortcutF11, SendShortcutShiftClick, type ActionShortcut } from "@graphite/messages";
|
||||
|
||||
const SHOW_TOOLTIP_DELAY_MS = 500;
|
||||
|
||||
export function createTooltipState() {
|
||||
export function createTooltipState(editor: Editor) {
|
||||
const { subscribe, update } = writable({
|
||||
visible: false,
|
||||
element: undefined as Element | undefined,
|
||||
position: { x: 0, y: 0 },
|
||||
shiftClickShortcut: undefined as ActionShortcut | undefined,
|
||||
altClickShortcut: undefined as ActionShortcut | undefined,
|
||||
f11Shortcut: undefined as ActionShortcut | undefined,
|
||||
});
|
||||
|
||||
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
|
|
@ -45,6 +51,25 @@ export function createTooltipState() {
|
|||
}, SHOW_TOOLTIP_DELAY_MS);
|
||||
});
|
||||
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutShiftClick, async (data) => {
|
||||
update((state) => {
|
||||
state.shiftClickShortcut = data.shortcut;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutAltClick, async (data) => {
|
||||
update((state) => {
|
||||
state.altClickShortcut = data.shortcut;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutF11, async (data) => {
|
||||
update((state) => {
|
||||
state.f11Shortcut = data.shortcut;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("mousedown", closeTooltip);
|
||||
document.addEventListener("keydown", closeTooltip);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use glam::DVec2;
|
|||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
|
||||
use std::ops::Sub;
|
||||
|
||||
/// Represents different ways of calculating the centroid.
|
||||
/// Represents different geometric interpretations of calculating the centroid (center of mass).
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum CentroidType {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ impl From<Fill> for FillChoice {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum describing the type of [Fill].
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fn math<T: num_traits::float::Float>(
|
|||
/// The value of "A" when calculating the expression.
|
||||
#[implementations(f64, f32)]
|
||||
operand_a: T,
|
||||
/// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2".
|
||||
/// A math expression that may incorporate "A" and/or "B", such as `sqrt(A + B) - B^2`.
|
||||
#[default(A + B)]
|
||||
expression: String,
|
||||
/// The value of "B" when calculating the expression.
|
||||
|
|
@ -76,98 +76,100 @@ fn math<T: num_traits::float::Float>(
|
|||
}
|
||||
}
|
||||
|
||||
/// The addition operation (+) calculates the sum of two numbers.
|
||||
/// The addition operation (`+`) calculates the sum of two scalar numbers or vectors.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
fn add<A: Add<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The left-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
augend: U,
|
||||
augend: A,
|
||||
/// The right-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
addend: B,
|
||||
) -> <A as Add<B>>::Output {
|
||||
augend + addend
|
||||
}
|
||||
|
||||
/// The subtraction operation (-) calculates the difference between two numbers.
|
||||
/// The subtraction operation (`-`) calculates the difference between two scalar numbers or vectors.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn subtract<U: Sub<T>, T>(
|
||||
fn subtract<A: Sub<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The left-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
minuend: U,
|
||||
minuend: A,
|
||||
/// The right-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
subtrahend: B,
|
||||
) -> <A as Sub<B>>::Output {
|
||||
minuend - subtrahend
|
||||
}
|
||||
|
||||
/// The multiplication operation (×) calculates the product of two numbers.
|
||||
/// The multiplication operation (`×`) calculates the product of two scalar numbers, vectors, or transforms.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn multiply<U: Mul<T>, T>(
|
||||
fn multiply<A: Mul<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The left-hand side of the multiplication operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2, DAffine2)]
|
||||
multiplier: U,
|
||||
multiplier: A,
|
||||
/// The right-hand side of the multiplication operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64, DAffine2)]
|
||||
multiplicand: T,
|
||||
) -> <U as Mul<T>>::Output {
|
||||
multiplicand: B,
|
||||
) -> <A as Mul<B>>::Output {
|
||||
multiplier * multiplicand
|
||||
}
|
||||
|
||||
/// The division operation (÷) calculates the quotient of two numbers.
|
||||
/// The division operation (`÷`) calculates the quotient of two scalar numbers or vectors.
|
||||
///
|
||||
/// Produces 0 if the denominator is 0.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>(
|
||||
fn divide<A: Div<B> + Default + PartialEq, B: Default + PartialEq>(
|
||||
_: impl Ctx,
|
||||
/// The left-hand side of the division operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
numerator: A,
|
||||
/// The right-hand side of the division operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
denominator: T,
|
||||
) -> <U as Div<T>>::Output
|
||||
denominator: B,
|
||||
) -> <A as Div<B>>::Output
|
||||
where
|
||||
<U as Div<T>>::Output: Default,
|
||||
<A as Div<B>>::Output: Default,
|
||||
{
|
||||
if denominator == T::default() {
|
||||
return <U as Div<T>>::Output::default();
|
||||
if denominator == B::default() {
|
||||
return <A as Div<B>>::Output::default();
|
||||
}
|
||||
numerator / denominator
|
||||
}
|
||||
|
||||
/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled.
|
||||
/// The modulo operation (`%`) calculates the remainder from the division of two scalar numbers or vectors.
|
||||
///
|
||||
/// The sign of the result shares the sign of the numerator unless *Always Positive* is enabled.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
||||
fn modulo<A: Rem<B, Output: Add<B, Output: Rem<B, Output = A::Output>>>, B: Copy>(
|
||||
_: impl Ctx,
|
||||
/// The left-hand side of the modulo operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
numerator: A,
|
||||
/// The right-hand side of the modulo operation.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
modulus: T,
|
||||
/// Ensures the result will always be positive, even if the numerator is negative.
|
||||
modulus: B,
|
||||
/// Ensures the result is always positive, even if the numerator is negative.
|
||||
#[default(true)]
|
||||
always_positive: bool,
|
||||
) -> <U as Rem<T>>::Output {
|
||||
) -> <A as Rem<B>>::Output {
|
||||
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
|
||||
}
|
||||
|
||||
/// The exponent operation (^) calculates the result of raising a number to a power.
|
||||
/// The exponent operation (`^`) calculates the result of raising a number to a power.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<T: Pow<T>>(
|
||||
_: impl Ctx,
|
||||
/// The base number that will be raised to the power.
|
||||
/// The base number that is raised to the power.
|
||||
#[implementations(f64, f32, u32)]
|
||||
base: T,
|
||||
/// The power to which the base number will be raised.
|
||||
/// The power to which the base number is raised.
|
||||
#[implementations(f64, f32, u32)]
|
||||
#[default(2.)]
|
||||
power: T,
|
||||
|
|
@ -175,15 +177,18 @@ fn exponent<T: Pow<T>>(
|
|||
base.pow(power)
|
||||
}
|
||||
|
||||
/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n.
|
||||
/// The `n`th root operation (`√`) calculates the inverse of exponentiation. Square root inverts squaring, cube root inverts cubing, and so on.
|
||||
///
|
||||
/// This is equivalent to raising the number to the power of `1/n`.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the nth root will be calculated.
|
||||
/// The number inside the radical for which the `n`th root is calculated.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: T,
|
||||
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
|
||||
/// Degrees 0 or less are invalid and will produce an output of 0.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
degree: T,
|
||||
|
|
@ -192,16 +197,18 @@ fn root<T: num_traits::float::Float>(
|
|||
radicand.sqrt()
|
||||
} else if degree == T::from(3.).unwrap() {
|
||||
radicand.cbrt()
|
||||
} else if degree <= T::from(0.).unwrap() {
|
||||
T::from(0.).unwrap()
|
||||
} else {
|
||||
radicand.powf(T::from(1.).unwrap() / degree)
|
||||
}
|
||||
}
|
||||
|
||||
/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e".
|
||||
/// The logarithmic function (`log`) calculates the logarithm of a number with a specified base. If the natural logarithm function (`ln`) is desired, set the base to "e".
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the logarithm will be calculated.
|
||||
/// The number for which the logarithm is calculated.
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
|
||||
|
|
@ -220,7 +227,7 @@ fn logarithm<T: num_traits::float::Float>(
|
|||
}
|
||||
}
|
||||
|
||||
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
/// The sine trigonometric function (`sin`) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -233,7 +240,7 @@ fn sine<T: num_traits::float::Float>(
|
|||
if radians { theta.sin() } else { theta.to_radians().sin() }
|
||||
}
|
||||
|
||||
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
/// The cosine trigonometric function (`cos`) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -246,7 +253,7 @@ fn cosine<T: num_traits::float::Float>(
|
|||
if radians { theta.cos() } else { theta.to_radians().cos() }
|
||||
}
|
||||
|
||||
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
/// The tangent trigonometric function (`tan`) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -259,41 +266,43 @@ fn tangent<T: num_traits::float::Float>(
|
|||
if radians { theta.tan() } else { theta.to_radians().tan() }
|
||||
}
|
||||
|
||||
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
||||
/// The inverse sine trigonometric function (`asin`) calculates the angle whose sine is the input value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine_inverse<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
/// The given value for which the angle is calculated. Must be in the domain `[-1, 1]` (it will be clamped to -1 or 1 otherwise).
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> T {
|
||||
if radians { value.asin() } else { value.asin().to_degrees() }
|
||||
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).asin();
|
||||
if radians { angle } else { angle.to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
||||
/// The inverse cosine trigonometric function (`acos`) calculates the angle whose cosine is the input value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine_inverse<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
/// The given value for which the angle is calculated. Must be in the domain `[-1, 1]` (it will be clamped to -1 or 1 otherwise).
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> T {
|
||||
if radians { value.acos() } else { value.acos().to_degrees() }
|
||||
let angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).acos();
|
||||
if radians { angle } else { angle.to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
|
||||
/// atan: the angle whose tangent is the specified scalar number.
|
||||
/// atan2: the angle of a ray from the origin to the specified vec2.
|
||||
/// The inverse tangent trigonometric function (`atan` or `atan2`, depending on input type) calculates:
|
||||
/// `atan`: the angle whose tangent is the input scalar number.
|
||||
/// `atan2`: the angle of a ray from the origin to the input vec2.
|
||||
///
|
||||
/// The resulting angle is always in the range [-90°, 90°] or, in radians, [-π/2, π/2].
|
||||
/// The resulting angle is always in the range `[-90°, 90°]` or, in radians, `[-π/2, π/2]`.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent_inverse<T: TangentInverse>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated.
|
||||
/// The given value for which the angle is calculated.
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: T,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
|
|
@ -325,18 +334,30 @@ impl TangentInverse for DVec2 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Linearly maps an input value from one range to another. The ranges may be reversed.
|
||||
///
|
||||
/// For example, 0.5 in the input range `[0, 1]` would map to 0 in the output range `[-180, 180]`.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn remap<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] value: U,
|
||||
#[implementations(f64, f32)] input_min: U,
|
||||
/// The value to be mapped between ranges.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// The lower bound of the input range.
|
||||
#[implementations(f64, f32)]
|
||||
input_min: U,
|
||||
/// The upper bound of the input range.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(1.)]
|
||||
input_max: U,
|
||||
#[implementations(f64, f32)] output_min: U,
|
||||
/// The lower bound of the output range.
|
||||
#[implementations(f64, f32)]
|
||||
output_min: U,
|
||||
/// The upper bound of the output range.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(1.)]
|
||||
output_max: U,
|
||||
/// Whether to constrain the result within the output range instead of extrapolating beyond its bounds.
|
||||
clamped: bool,
|
||||
) -> U {
|
||||
let input_range = input_max - input_min;
|
||||
|
|
@ -363,17 +384,17 @@ fn remap<U: num_traits::float::Float>(
|
|||
}
|
||||
}
|
||||
|
||||
/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
/// The random function (`rand`) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn random(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
/// Seed to determine the unique variation of which number will be generated.
|
||||
/// Seed to determine the unique variation of which number is generated.
|
||||
seed: u64,
|
||||
/// The smaller end of the range within which the random number will be generated.
|
||||
/// The smaller end of the range within which the random number is generated.
|
||||
#[default(0.)]
|
||||
min: f64,
|
||||
/// The larger end of the range within which the random number will be generated.
|
||||
/// The larger end of the range within which the random number is generated.
|
||||
#[default(1.)]
|
||||
max: f64,
|
||||
) -> f64 {
|
||||
|
|
@ -404,89 +425,89 @@ fn to_f64(_: impl Ctx, value: f64) -> f64 {
|
|||
value
|
||||
}
|
||||
|
||||
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
/// The rounding function (`round`) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded.
|
||||
/// The number to be rounded to the nearest whole number.
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.round()
|
||||
}
|
||||
|
||||
/// The floor function (floor) rounds down an input value to the nearest whole number, unless the input number is already whole.
|
||||
/// The floor function (`floor`) rounds down an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded down.
|
||||
/// The number to be rounded down.
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.floor()
|
||||
}
|
||||
|
||||
/// The ceiling function (ceil) rounds up an input value to the nearest whole number, unless the input number is already whole.
|
||||
/// The ceiling function (`ceil`) rounds up an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded up.
|
||||
/// The number to be rounded up.
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.ceil()
|
||||
}
|
||||
|
||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||
/// The absolute value function (`abs`) removes the negative sign from an input value, if present.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value<T: num_traits::sign::Signed>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be made positive.
|
||||
/// The number to be made positive.
|
||||
#[implementations(f64, f32, i32, i64)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.abs()
|
||||
}
|
||||
|
||||
/// The minimum function (min) picks the smaller of two numbers.
|
||||
/// The minimum function (`min`) picks the smaller of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the lesser will be returned.
|
||||
/// One of the two numbers, of which the lesser is returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the lesser will be returned.
|
||||
/// The other of the two numbers, of which the lesser is returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value < other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
/// The maximum function (max) picks the larger of two numbers.
|
||||
/// The maximum function (`max`) picks the larger of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the greater will be returned.
|
||||
/// One of the two numbers, of which the greater is returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the greater will be returned.
|
||||
/// The other of the two numbers, of which the greater is returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value > other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
/// The clamp function (`clamp`) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn clamp<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// The number to be clamped, which will be restricted to the range between the minimum and maximum values.
|
||||
/// The number to be clamped, which is restricted to the range between the minimum and maximum values.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The left (smaller) side of the range. The output will never be less than this number.
|
||||
/// The left (smaller) side of the range. The output is never less than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
min: T,
|
||||
/// The right (greater) side of the range. The output will never be greater than this number.
|
||||
/// The right (greater) side of the range. The output is never greater than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
max: T,
|
||||
) -> T {
|
||||
|
|
@ -504,10 +525,10 @@ fn clamp<T: std::cmp::PartialOrd>(
|
|||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn greatest_common_divisor<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32> + std::ops::SubAssign>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers for which the GCD will be calculated.
|
||||
/// One of the two numbers for which the GCD is calculated.
|
||||
#[implementations(u32, u64, i32)]
|
||||
value: T,
|
||||
/// The other of the two numbers for which the GCD will be calculated.
|
||||
/// The other of the two numbers for which the GCD is calculated.
|
||||
#[implementations(u32, u64, i32)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
|
|
@ -524,10 +545,10 @@ fn greatest_common_divisor<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32
|
|||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn least_common_multiple<T: num_traits::ToPrimitive + num_traits::FromPrimitive + num_traits::identities::Zero>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers for which the LCM will be calculated.
|
||||
/// One of the two numbers for which the LCM is calculated.
|
||||
#[implementations(u32, u64, i32)]
|
||||
value: T,
|
||||
/// The other of the two numbers for which the LCM will be calculated.
|
||||
/// The other of the two numbers for which the LCM is calculated.
|
||||
#[implementations(u32, u64, i32)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
|
|
@ -574,36 +595,8 @@ fn binary_gcd<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32> + std::ops:
|
|||
a << shift
|
||||
}
|
||||
|
||||
/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<T: std::cmp::PartialEq<T>>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str, String)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str, String)]
|
||||
other_value: T,
|
||||
) -> bool {
|
||||
other_value == value
|
||||
}
|
||||
|
||||
/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn not_equals<T: std::cmp::PartialEq<T>>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str)]
|
||||
other_value: T,
|
||||
) -> bool {
|
||||
other_value != value
|
||||
}
|
||||
|
||||
/// The less-than operation (<) compares two values and returns true if the first value is less than the second, or false if it is not.
|
||||
/// If enabled with "Or Equal", the less-than-or-equal operation (<=) will be used instead.
|
||||
/// The less-than operation (`<`) compares two values and returns true if the first value is less than the second, or false if it is not.
|
||||
/// If enabled with *Or Equal*, the less-than-or-equal operation (`<=`) is used instead.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn less_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -613,14 +606,14 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
|
|||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the less-than-or-equal operation (<=) instead of the less-than operation (<).
|
||||
/// Uses the less-than-or-equal operation (`<=`) instead of the less-than operation (`<`).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value <= other_value } else { value < other_value }
|
||||
}
|
||||
|
||||
/// The greater-than operation (>) compares two values and returns true if the first value is greater than the second, or false if it is not.
|
||||
/// If enabled with "Or Equal", the greater-than-or-equal operation (>=) will be used instead.
|
||||
/// The greater-than operation (`>`) compares two values and returns true if the first value is greater than the second, or false if it is not.
|
||||
/// If enabled with *Or Equal*, the greater-than-or-equal operation (`>=`) is used instead.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn greater_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -630,13 +623,41 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
|
|||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the greater-than-or-equal operation (>=) instead of the greater-than operation (>).
|
||||
/// Uses the greater-than-or-equal operation (`>=`) instead of the greater-than operation (`>`).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value >= other_value } else { value > other_value }
|
||||
}
|
||||
|
||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||
/// The equality operation (`==`, `XNOR`) compares two values and returns true if they are equal, or false if they are not.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<T: std::cmp::PartialEq<T>>(
|
||||
_: impl Ctx,
|
||||
/// One of the two values to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str, String)]
|
||||
value: T,
|
||||
/// The other of the two values to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str, String)]
|
||||
other_value: T,
|
||||
) -> bool {
|
||||
other_value == value
|
||||
}
|
||||
|
||||
/// The inequality operation (`!=`, `XOR`) compares two values and returns true if they are not equal, or false if they are.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn not_equals<T: std::cmp::PartialEq<T>>(
|
||||
_: impl Ctx,
|
||||
/// One of the two values to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str)]
|
||||
value: T,
|
||||
/// The other of the two values to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, bool, &str)]
|
||||
other_value: T,
|
||||
) -> bool {
|
||||
other_value != value
|
||||
}
|
||||
|
||||
/// The logical OR operation (`||`) returns true if either of the two inputs are true, or false if both are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(
|
||||
_: impl Ctx,
|
||||
|
|
@ -648,7 +669,7 @@ fn logical_or(
|
|||
value || other_value
|
||||
}
|
||||
|
||||
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
||||
/// The logical AND operation (`&&`) returns true if both of the two inputs are true, or false if any are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_and(
|
||||
_: impl Ctx,
|
||||
|
|
@ -660,7 +681,7 @@ fn logical_and(
|
|||
value && other_value
|
||||
}
|
||||
|
||||
/// The logical not operation (!) reverses true and false value of the input.
|
||||
/// The logical NOT operation (`!`) reverses true and false value of the input.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(
|
||||
_: impl Ctx,
|
||||
|
|
@ -736,20 +757,39 @@ fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100
|
|||
}
|
||||
}
|
||||
|
||||
/// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths.
|
||||
///
|
||||
/// Calculated as `‖a‖‖b‖cos(θ)`, it represents the product of their lengths (`‖a‖‖b‖`) scaled by the alignment of their directions (`cos(θ)`).
|
||||
/// The output ranges from the positive to negative product of their lengths based on when they are pointing in the same or opposite directions.
|
||||
/// If any vector has zero length, the output is 0.
|
||||
#[node_macro::node(category("Math: Vector"))]
|
||||
fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||
vector_a.dot(vector_b)
|
||||
fn dot_product(
|
||||
_: impl Ctx,
|
||||
/// An operand of the dot product operation.
|
||||
vector_a: DVec2,
|
||||
/// The other operand of the dot product operation.
|
||||
#[default((1., 0.))]
|
||||
vector_b: DVec2,
|
||||
/// Whether to normalize both input vectors so the calculation ranges in `[-1, 1]` by considering only their degree of directional alignment.
|
||||
normalize: bool,
|
||||
) -> f64 {
|
||||
if normalize {
|
||||
vector_a.normalize_or_zero().dot(vector_b.normalize_or_zero())
|
||||
} else {
|
||||
vector_a.dot(vector_b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the length or magnitude of a vector.
|
||||
// TODO: Rename to "Magnitude"
|
||||
/// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow represented by the vector.
|
||||
#[node_macro::node(category("Math: Vector"))]
|
||||
fn length(_: impl Ctx, vector: DVec2) -> f64 {
|
||||
vector.length()
|
||||
}
|
||||
|
||||
/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude.
|
||||
/// Scales the input vector to unit length while preserving its direction. This is equivalent to dividing the input vector by its own magnitude.
|
||||
///
|
||||
/// Returns zero when the input vector is zero.
|
||||
/// Returns 0 when the input vector has zero length.
|
||||
#[node_macro::node(category("Math: Vector"))]
|
||||
fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 {
|
||||
vector.normalize_or_zero()
|
||||
|
|
@ -765,7 +805,7 @@ mod test {
|
|||
pub fn dot_product_function() {
|
||||
let vector_a = DVec2::new(1., 2.);
|
||||
let vector_b = DVec2::new(3., 4.);
|
||||
assert_eq!(dot_product((), vector_a, vector_b), 11.);
|
||||
assert_eq!(dot_product((), vector_a, vector_b, false), 11.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -574,7 +574,6 @@ fn vibrance<T: Adjust<Color>>(
|
|||
image
|
||||
}
|
||||
|
||||
/// Color Channel
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
|
|
@ -586,7 +585,6 @@ pub enum RedGreenBlue {
|
|||
Blue,
|
||||
}
|
||||
|
||||
/// Color Channel
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
|
|
@ -599,7 +597,7 @@ pub enum RedGreenBlueAlpha {
|
|||
Alpha,
|
||||
}
|
||||
|
||||
/// Style of noise pattern
|
||||
/// Style of noise pattern.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Dropdown)]
|
||||
|
|
@ -616,9 +614,9 @@ pub enum NoiseType {
|
|||
WhiteNoise,
|
||||
}
|
||||
|
||||
/// Style of layered levels of the noise pattern.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
/// Style of layered levels of the noise pattern
|
||||
pub enum FractalType {
|
||||
#[default]
|
||||
None,
|
||||
|
|
@ -632,7 +630,7 @@ pub enum FractalType {
|
|||
DomainWarpIndependent,
|
||||
}
|
||||
|
||||
/// Distance function used by the cellular noise
|
||||
/// Distance function used by the cellular noise.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
pub enum CellularDistanceFunction {
|
||||
|
|
@ -663,7 +661,6 @@ pub enum CellularReturnType {
|
|||
Division,
|
||||
}
|
||||
|
||||
/// Type of domain warp
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Dropdown)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue