Add some additional image effect nodes (#869)

* Move the Subpath type to graphene-std

* Add the transform subpath node

* Delete selected nodes

* Inserting node list on right click

* Add several bitmap manipulator nodes

* Convert add node to use f64

* Add posterize node

* Rename names randomly

* Fix naming

* Exposure node

* Fix typo

* Adjust exposure node range

* Comment out vector nodes

* Adjust exposure range again

* Posterise as ints

* Rename input

* Use >= in the to hsl function
This commit is contained in:
0HyperCube
2022-12-03 14:29:45 -08:00
committed by GitHub
parent 0fb12bf39b
commit f9d772070b
50 changed files with 1310 additions and 481 deletions
+2 -11
View File
@@ -40,27 +40,18 @@
--color-f-white-rgb: 255, 255, 255;
--color-data-general: #c5c5c5;
--color-data-general-rgb: 197, 197, 197;
--color-data-general-dim: #767676;
--color-data-general-dim-rgb: 118, 118, 118;
--color-data-vector: #65bbe5;
--color-data-vector-rgb: 101, 187, 229;
--color-data-vector-dim: #4b778c;
--color-data-vector-dim-rgb: 75, 119, 140;
--color-data-raster: #e4bb72;
--color-data-raster-rgb: 228, 187, 114;
--color-data-raster-dim: #8b7752;
--color-data-raster-dim-rgb: 139, 119, 82;
--color-data-mask: #8d85c7;
--color-data-mask-rgb: 141, 133, 199;
--color-data-number: #d6536e;
--color-data-number-rgb: 214, 83, 110;
--color-data-number-dim: #803242;
--color-data-number-dim-rgb: 128, 50, 66;
--color-data-vec2: #cc00ff;
--color-data-vec2-dim: #71008d;
--color-data-color: #70a898;
--color-data-color-rgb: 112, 168, 152;
--color-data-color-dim: #43645b;
--color-data-color-dim-rgb: 67, 100, 91;
--color-none: white;
--color-none-repeat: no-repeat;
+50 -16
View File
@@ -1,12 +1,6 @@
<template>
<LayoutCol class="node-graph">
<LayoutRow class="options-bar"></LayoutRow>
<div class="node-list">
<LayoutRow>Nodes:</LayoutRow>
<LayoutRow>
<TextButton v-for="nodeType in nodeTypes" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)"></TextButton>
</LayoutRow>
</div>
<LayoutRow
class="graph"
ref="graph"
@@ -21,6 +15,14 @@
'--dot-radius': `${dotRadius}px`,
}"
>
<LayoutCol class="node-list" v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
<TextButton v-for="nodeType in nodeCategory[1]" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)" />
</LayoutCol>
<TextLabel v-if="nodeCategories.length === 0">No search results :(</TextLabel>
</LayoutCol>
<div
class="nodes"
ref="nodesContainer"
@@ -37,8 +39,6 @@
:style="{
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
'--data-color': 'var(--color-data-raster)',
'--data-color-dim': 'var(--color-data-raster-dim)',
}"
:data-node="node.id"
>
@@ -111,10 +111,9 @@
.node-list {
width: max-content;
position: fixed;
padding: 20px;
margin: 40px 10px;
padding: 5px;
z-index: 3;
background-color: var(--color-4-dimgray);
background-color: var(--color-3-darkgray);
}
.options-bar {
@@ -181,7 +180,7 @@
border-radius: 4px;
background: var(--color-4-dimgray);
left: calc((var(--offset-left) + 0.5) * 24px);
top: calc((var(--offset-top) + 0.5) * 24px);
top: calc((var(--offset-top) - 0.5) * 24px);
&.selected {
border: 1px solid var(--color-e-nearwhite);
@@ -209,6 +208,7 @@
.arguments {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
@@ -297,6 +297,7 @@ import type { IconName } from "@/utility-functions/icons";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import TextButton from "@/components/widgets/buttons/TextButton.vue";
import TextInput from "@/components/widgets/inputs/TextInput.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
@@ -316,6 +317,8 @@ export default defineComponent({
linkInProgressFromConnector: undefined as HTMLDivElement | undefined,
linkInProgressToConnector: undefined as HTMLDivElement | DOMRect | undefined,
nodeLinkPaths: [] as [string, string][],
searchTerm: "",
nodeListLocation: undefined as { x: number; y: number } | undefined,
};
},
computed: {
@@ -335,8 +338,24 @@ export default defineComponent({
nodes() {
return this.nodeGraph.state.nodes;
},
nodeTypes() {
return this.nodeGraph.state.nodeTypes;
nodeCategories() {
const categories = new Map();
this.nodeGraph.state.nodeTypes.forEach((node) => {
if (this.searchTerm.length && !node.name.toLowerCase().includes(this.searchTerm.toLowerCase()) && !node.category.toLowerCase().includes(this.searchTerm.toLowerCase())) return;
const category = categories.get(node.category);
if (category) category.push(node);
else categories.set(node.category, [node]);
});
const result = Array.from(categories);
return result;
},
nodeListX() {
return ((this.nodeListLocation?.x || 0) * GRID_SIZE + this.transform.x) * this.transform.scale;
},
nodeListY() {
return ((this.nodeListLocation?.y || 0) * GRID_SIZE + this.transform.y) * this.transform.scale;
},
linkPathInProgress(): [string, string] | undefined {
if (this.linkInProgressFromConnector && this.linkInProgressToConnector) {
@@ -461,8 +480,19 @@ export default defineComponent({
}
},
pointerDown(e: PointerEvent) {
if (e.button === 2) {
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
const graph = graphDiv?.getBoundingClientRect() || new DOMRect();
this.nodeListLocation = {
x: Math.round(((e.clientX - graph.x) / this.transform.scale - this.transform.x) / GRID_SIZE),
y: Math.round(((e.clientY - graph.y) / this.transform.scale - this.transform.y) / GRID_SIZE),
};
return;
}
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
const nodeList = (e.target as HTMLElement).closest(".node-list") as HTMLElement | undefined;
if (port) {
const isOutput = Boolean(port.getAttribute("data-port") === "output");
@@ -488,7 +518,7 @@ export default defineComponent({
}
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
} else {
} else if (!nodeList) {
this.selected = [];
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
@@ -560,7 +590,10 @@ export default defineComponent({
this.linkInProgressToConnector = undefined;
},
createNode(nodeType: string): void {
this.editor.instance.createNode(nodeType);
if (!this.nodeListLocation) return;
this.editor.instance.createNode(nodeType, this.nodeListLocation.x, this.nodeListLocation.y);
this.nodeListLocation = undefined;
},
},
mounted() {
@@ -578,6 +611,7 @@ export default defineComponent({
LayoutRow,
TextLabel,
TextButton,
TextInput,
},
});
</script>
@@ -1,6 +1,6 @@
<template>
<LayoutRow class="parameter-expose-button">
<button :class="{ exposed }" :style="{ '--data-type-color': dataTypeColor }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
<button :class="{ exposed }" :style="{ '--data-type-color': `var(--color-data-${dataType})` }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
</LayoutRow>
</template>
@@ -53,21 +53,6 @@ export default defineComponent({
// Callbacks
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
},
computed: {
dataTypeColor(): string {
// TODO: Move this function somewhere where it can be reused by other components
const colorsMap = {
general: "var(--color-data-general)",
vector: "var(--color-data-vector)",
raster: "var(--color-data-raster)",
mask: "var(--color-data-mask)",
number: "var(--color-data-number)",
color: "var(--color-data-color)",
} as const;
return colorsMap[this.dataType as keyof typeof colorsMap] || colorsMap.general;
},
},
components: { LayoutRow },
});
</script>
@@ -10,6 +10,7 @@
v-model="inputValue"
:spellcheck="spellcheck"
:disabled="disabled"
:placeholder="placeholder"
@focus="() => $emit('textFocused')"
@blur="() => $emit('textChanged')"
@change="() => $emit('textChanged')"
@@ -148,6 +149,7 @@ export default defineComponent({
textarea: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
placeholder: { type: String as PropType<string>, required: false },
},
data() {
return {
@@ -7,6 +7,7 @@
:spellcheck="true"
:disabled="disabled"
:tooltip="tooltip"
:placeholder="placeholder"
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
:sharpRightCorners="sharpRightCorners"
@textFocused="() => onTextFocused()"
@@ -41,6 +42,7 @@ export default defineComponent({
// Label
label: { type: String as PropType<string>, required: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
placeholder: { type: String as PropType<string>, required: false },
// Disabled
disabled: { type: Boolean as PropType<boolean>, default: false },
+16 -1
View File
@@ -17,5 +17,20 @@ import App from "@/App.vue";
await initWasm();
// Initialize the Vue application
createApp(App).mount("#app");
createApp(App)
.directive("focus", {
// When the bound element is inserted into the DOM
mounted(el) {
let focus = el;
// Find actual relevant child
while (focus.children.length) focus = focus.children[0];
// Random timeout needed?
setTimeout(() => {
focus.focus(); // Focus the element
}, 0);
},
})
.mount("#app");
};
@@ -102,6 +102,8 @@ export class FrontendNodeLink {
export class FrontendNodeType {
readonly name!: string;
readonly category!: string;
}
export class IndexedDbDocumentDetails extends DocumentDetails {