Skip to content
253 changes: 171 additions & 82 deletions src/components/maskeditor/BrushSettingsPanel.vue
Original file line number Diff line number Diff line change
@@ -1,129 +1,218 @@
<template>
<div class="flex flex-col gap-3 pb-3">
<h3
class="text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5"
>
<h3 class="text-center text-[15px] font-sans text-descrip-text mt-2.5">
{{ t('maskEditor.brushSettings') }}
</h3>

<button :class="textButtonClass" @click="resetToDefault">
{{ t('maskEditor.resetToDefault') }}
</button>

<!-- Brush Shape -->
<div class="flex flex-col gap-3 pb-3">
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
t('maskEditor.brushShape')
}}</span>
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.brushShape') }}
</span>

<div
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
class="flex flex-row gap-2.5 items-center h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
>
<div
class="maskEditor_sidePanelBrushShapeCircle bg-transparent hover:bg-comfy-menu-bg"
:class="{ active: store.brushSettings.type === BrushShape.Arc }"
:style="{
background:
class="maskEditor_sidePanelBrushShapeCircle hover:bg-comfy-menu-bg"
:class="
cn(
store.brushSettings.type === BrushShape.Arc
? 'var(--p-button-text-primary-color)'
: ''
}"
? 'bg-[var(--p-button-text-primary-color)] active'
: 'bg-transparent'
)
"
@click="setBrushShape(BrushShape.Arc)"
></div>

<div
class="maskEditor_sidePanelBrushShapeSquare bg-transparent hover:bg-comfy-menu-bg"
:class="{ active: store.brushSettings.type === BrushShape.Rect }"
:style="{
background:
class="maskEditor_sidePanelBrushShapeSquare hover:bg-comfy-menu-bg"
:class="
cn(
store.brushSettings.type === BrushShape.Rect
? 'var(--p-button-text-primary-color)'
: ''
}"
? 'bg-[var(--p-button-text-primary-color)] active'
: 'bg-transparent'
)
"
@click="setBrushShape(BrushShape.Rect)"
></div>
</div>
</div>

<!-- Color -->
<div class="flex flex-col gap-3 pb-3">
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
t('maskEditor.colorSelector')
}}</span>
<input type="color" :value="store.rgbColor" @input="onColorChange" />
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.colorSelector') }}
</span>
<input
ref="colorInputRef"
v-model="store.rgbColor"
type="color"
class="h-10 rounded-md cursor-pointer"
/>
</div>

<!-- Thickness -->
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.thickness') }}
</span>
<input
v-model.number="brushSize"
type="number"
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
:min="1"
:max="250"
:step="1"
/>
</div>
<SliderControl
v-model="brushSize"
class="flex-1"
label=""
:min="1"
:max="250"
:step="1"
/>
</div>

<!-- Opacity -->
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.opacity') }}
</span>
<input
v-model.number="brushOpacity"
type="number"
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
:min="0"
:max="1"
:step="0.01"
/>
</div>
<SliderControl
v-model="brushOpacity"
class="flex-1"
label=""
:min="0"
:max="1"
:step="0.01"
/>
</div>

<!-- Hardness -->
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.hardness') }}
</span>
<input
v-model.number="brushHardness"
type="number"
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
:min="0"
:max="1"
:step="0.01"
/>
</div>
<SliderControl
v-model="brushHardness"
class="flex-1"
label=""
:min="0"
:max="1"
:step="0.01"
/>
</div>

<SliderControl
:label="t('maskEditor.thickness')"
:min="1"
:max="500"
:step="1"
:model-value="store.brushSettings.size"
@update:model-value="onThicknessChange"
/>

<SliderControl
:label="t('maskEditor.opacity')"
:min="0"
:max="1"
:step="0.01"
:model-value="store.brushSettings.opacity"
@update:model-value="onOpacityChange"
/>

<SliderControl
:label="t('maskEditor.hardness')"
:min="0"
:max="1"
:step="0.01"
:model-value="store.brushSettings.hardness"
@update:model-value="onHardnessChange"
/>

<SliderControl
:label="$t('maskEditor.stepSize')"
:min="1"
:max="100"
:step="1"
:model-value="store.brushSettings.stepSize"
@update:model-value="onStepSizeChange"
/>
<!-- Step Size -->
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-left text-xs font-sans text-descrip-text">
{{ t('maskEditor.stepSize') }}
</span>
<input
v-model.number="brushStepSize"
type="number"
class="w-16 px-2 py-1 text-sm text-center border rounded-md bg-comfy-menu-bg border-p-form-field-border-color text-input-text"
:min="1"
:max="100"
:step="1"
/>
</div>
<SliderControl
v-model="brushStepSize"
class="flex-1"
label=""
:min="1"
:max="100"
:step="1"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'

import { BrushShape } from '@/extensions/core/maskeditor/types'
import { t } from '@/i18n'
import { useMaskEditorStore } from '@/stores/maskEditorStore'
import { cn } from '@/utils/tailwindUtil'

import SliderControl from './controls/SliderControl.vue'

const store = useMaskEditorStore()

const textButtonClass =
'h-7.5 w-32 rounded-[10px] border border-[var(--p-form-field-border-color)] text-[var(--input-text)] font-sans pointer-events-auto transition-colors duration-100 bg-[var(--comfy-menu-bg)] hover:bg-secondary-background-hover'
const colorInputRef = ref<HTMLInputElement>()

const textButtonClass =
'h-7.5 w-32 rounded-[10px] border border-p-form-field-border-color text-input-text font-sans transition-colors duration-100 bg-comfy-menu-bg hover:bg-secondary-background-hover'

/* Computed properties that use store setters for validation */
const brushSize = computed({
get: () => store.brushSettings.size,
set: (value: number) => store.setBrushSize(value)
})

const brushOpacity = computed({
get: () => store.brushSettings.opacity,
set: (value: number) => store.setBrushOpacity(value)
})

const brushHardness = computed({
get: () => store.brushSettings.hardness,
set: (value: number) => store.setBrushHardness(value)
})

const brushStepSize = computed({
get: () => store.brushSettings.stepSize,
set: (value: number) => store.setBrushStepSize(value)
})

/* Brush shape */
const setBrushShape = (shape: BrushShape) => {
store.brushSettings.type = shape
}

const onColorChange = (event: Event) => {
store.rgbColor = (event.target as HTMLInputElement).value
}

const onThicknessChange = (value: number) => {
store.setBrushSize(value)
}

const onOpacityChange = (value: number) => {
store.setBrushOpacity(value)
}

const onHardnessChange = (value: number) => {
store.setBrushHardness(value)
}

const onStepSizeChange = (value: number) => {
store.setBrushStepSize(value)
}

/* Reset */
const resetToDefault = () => {
store.resetBrushToDefault()
}

onMounted(() => {
if (colorInputRef.value) {
store.colorInput = colorInputRef.value
}
})

onBeforeUnmount(() => {
store.colorInput = null
})
</script>
1 change: 1 addition & 0 deletions src/components/maskeditor/MaskEditorContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ onBeforeUnmount(() => {
}

store.canvasHistory.clearStates()

store.resetState()
dataStore.reset()
})
Expand Down
33 changes: 22 additions & 11 deletions src/extensions/core/maskeditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ function isOpened(): boolean {
return useDialogStore().isDialogOpen('global-mask-editor')
}

const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => {
if (!isOpened()) return

const store = useMaskEditorStore()
const oldBrushSize = store.brushSettings.size
const newBrushSize = sizeChanger(oldBrushSize)
store.setBrushSize(newBrushSize)
}

app.registerExtension({
name: 'Comfy.MaskEditor',
settings: [
Expand Down Expand Up @@ -82,13 +91,24 @@ app.registerExtension({
id: 'Comfy.MaskEditor.BrushSize.Increase',
icon: 'pi pi-plus-circle',
label: 'Increase Brush Size in MaskEditor',
function: () => changeBrushSize((old) => _.clamp(old + 4, 1, 100))
function: () => changeBrushSize((old) => _.clamp(old + 2, 1, 250))
},
{
id: 'Comfy.MaskEditor.BrushSize.Decrease',
icon: 'pi pi-minus-circle',
label: 'Decrease Brush Size in MaskEditor',
function: () => changeBrushSize((old) => _.clamp(old - 4, 1, 100))
function: () => changeBrushSize((old) => _.clamp(old - 2, 1, 250))
},
{
id: 'Comfy.MaskEditor.ColorPicker',
icon: 'pi pi-palette',
label: 'Open Color Picker in MaskEditor',
function: () => {
if (!isOpened()) return

const store = useMaskEditorStore()
store.colorInput?.click()
}
}
],
init() {
Expand All @@ -101,12 +121,3 @@ app.registerExtension({
)
}
})

const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => {
if (!isOpened()) return

const store = useMaskEditorStore()
const oldBrushSize = store.brushSettings.size
const newBrushSize = sizeChanger(oldBrushSize)
store.setBrushSize(newBrushSize)
}
6 changes: 5 additions & 1 deletion src/stores/maskEditorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export const useMaskEditorStore = defineStore('maskEditor', () => {

const tgpuRoot = ref<any>(null)

const colorInput = ref<HTMLInputElement | null>(null)

watch(maskCanvas, (canvas) => {
if (canvas) {
maskCtx.value = canvas.getContext('2d', { willReadFrequently: true })
Expand Down Expand Up @@ -113,7 +115,7 @@ export const useMaskEditorStore = defineStore('maskEditor', () => {
})

function setBrushSize(size: number): void {
brushSettings.value.size = _.clamp(size, 1, 500)
brushSettings.value.size = _.clamp(size, 1, 250)
}

function setBrushOpacity(opacity: number): void {
Expand Down Expand Up @@ -252,6 +254,8 @@ export const useMaskEditorStore = defineStore('maskEditor', () => {

tgpuRoot,

colorInput,

setBrushSize,
setBrushOpacity,
setBrushHardness,
Expand Down
Loading