Skip to content

Commit 825ec72

Browse files
brucew4yn3rpDrJKL
andauthored
Fixed Brush Settings Post Refactor and Added Numeric Control (#7783)
## Summary Refactored BrushSettingsPanel layout to stack labels and number inputs above sliders, and fixed brush size keybinding limits to match the updated 1-250 range. ## Changes - **What**: - Reorganized BrushSettingsPanel UI to display labels and number inputs in a row above each slider (instead of side-by-side), creating a cleaner vertical layout with better visual hierarchy. - Updated brush size increase/decrease keybindings to clamp between 1-250 (previously 1-100) to match the refactored slider limits. - Added setting for color picker keybinding - **Breaking**: None ## Review Focus - Verify the stacked layout (label + number input above slider) works well across different panel widths - Confirm all slider controls properly sync with their corresponding number inputs - Test brush size keybindings (increase/decrease) respect the new 1-250 limits ## Screenshot <img width="1713" height="848" alt="image" src="https://github.com/user-attachments/assets/22a26ad2-61be-4031-92d0-b4577a003552" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7783-Fixed-Brush-Settings-Port-Refactor-and-Added-Numeric-Control-2d76d73d365081bda7a8e12d3c649085) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <[email protected]>
1 parent 664aafb commit 825ec72

File tree

4 files changed

+199
-94
lines changed

4 files changed

+199
-94
lines changed
Lines changed: 171 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,218 @@
11
<template>
22
<div class="flex flex-col gap-3 pb-3">
3-
<h3
4-
class="text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5"
5-
>
3+
<h3 class="text-center text-[15px] font-sans text-descrip-text mt-2.5">
64
{{ t('maskEditor.brushSettings') }}
75
</h3>
86

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

11+
<!-- Brush Shape -->
1312
<div class="flex flex-col gap-3 pb-3">
14-
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
15-
t('maskEditor.brushShape')
16-
}}</span>
13+
<span class="text-left text-xs font-sans text-descrip-text">
14+
{{ t('maskEditor.brushShape') }}
15+
</span>
16+
1717
<div
18-
class="flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
18+
class="flex flex-row gap-2.5 items-center h-[50px] w-full rounded-[10px] bg-secondary-background-hover"
1919
>
2020
<div
21-
class="maskEditor_sidePanelBrushShapeCircle bg-transparent hover:bg-comfy-menu-bg"
22-
:class="{ active: store.brushSettings.type === BrushShape.Arc }"
23-
:style="{
24-
background:
21+
class="maskEditor_sidePanelBrushShapeCircle hover:bg-comfy-menu-bg"
22+
:class="
23+
cn(
2524
store.brushSettings.type === BrushShape.Arc
26-
? 'var(--p-button-text-primary-color)'
27-
: ''
28-
}"
25+
? 'bg-[var(--p-button-text-primary-color)] active'
26+
: 'bg-transparent'
27+
)
28+
"
2929
@click="setBrushShape(BrushShape.Arc)"
3030
></div>
31+
3132
<div
32-
class="maskEditor_sidePanelBrushShapeSquare bg-transparent hover:bg-comfy-menu-bg"
33-
:class="{ active: store.brushSettings.type === BrushShape.Rect }"
34-
:style="{
35-
background:
33+
class="maskEditor_sidePanelBrushShapeSquare hover:bg-comfy-menu-bg"
34+
:class="
35+
cn(
3636
store.brushSettings.type === BrushShape.Rect
37-
? 'var(--p-button-text-primary-color)'
38-
: ''
39-
}"
37+
? 'bg-[var(--p-button-text-primary-color)] active'
38+
: 'bg-transparent'
39+
)
40+
"
4041
@click="setBrushShape(BrushShape.Rect)"
4142
></div>
4243
</div>
4344
</div>
4445

46+
<!-- Color -->
4547
<div class="flex flex-col gap-3 pb-3">
46-
<span class="text-left text-xs font-sans text-[var(--descrip-text)]">{{
47-
t('maskEditor.colorSelector')
48-
}}</span>
49-
<input type="color" :value="store.rgbColor" @input="onColorChange" />
48+
<span class="text-left text-xs font-sans text-descrip-text">
49+
{{ t('maskEditor.colorSelector') }}
50+
</span>
51+
<input
52+
ref="colorInputRef"
53+
v-model="store.rgbColor"
54+
type="color"
55+
class="h-10 rounded-md cursor-pointer"
56+
/>
57+
</div>
58+
59+
<!-- Thickness -->
60+
<div class="flex flex-col gap-2">
61+
<div class="flex items-center justify-between">
62+
<span class="text-left text-xs font-sans text-descrip-text">
63+
{{ t('maskEditor.thickness') }}
64+
</span>
65+
<input
66+
v-model.number="brushSize"
67+
type="number"
68+
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"
69+
:min="1"
70+
:max="250"
71+
:step="1"
72+
/>
73+
</div>
74+
<SliderControl
75+
v-model="brushSize"
76+
class="flex-1"
77+
label=""
78+
:min="1"
79+
:max="250"
80+
:step="1"
81+
/>
82+
</div>
83+
84+
<!-- Opacity -->
85+
<div class="flex flex-col gap-2">
86+
<div class="flex items-center justify-between">
87+
<span class="text-left text-xs font-sans text-descrip-text">
88+
{{ t('maskEditor.opacity') }}
89+
</span>
90+
<input
91+
v-model.number="brushOpacity"
92+
type="number"
93+
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"
94+
:min="0"
95+
:max="1"
96+
:step="0.01"
97+
/>
98+
</div>
99+
<SliderControl
100+
v-model="brushOpacity"
101+
class="flex-1"
102+
label=""
103+
:min="0"
104+
:max="1"
105+
:step="0.01"
106+
/>
107+
</div>
108+
109+
<!-- Hardness -->
110+
<div class="flex flex-col gap-2">
111+
<div class="flex items-center justify-between">
112+
<span class="text-left text-xs font-sans text-descrip-text">
113+
{{ t('maskEditor.hardness') }}
114+
</span>
115+
<input
116+
v-model.number="brushHardness"
117+
type="number"
118+
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"
119+
:min="0"
120+
:max="1"
121+
:step="0.01"
122+
/>
123+
</div>
124+
<SliderControl
125+
v-model="brushHardness"
126+
class="flex-1"
127+
label=""
128+
:min="0"
129+
:max="1"
130+
:step="0.01"
131+
/>
50132
</div>
51133

52-
<SliderControl
53-
:label="t('maskEditor.thickness')"
54-
:min="1"
55-
:max="500"
56-
:step="1"
57-
:model-value="store.brushSettings.size"
58-
@update:model-value="onThicknessChange"
59-
/>
60-
61-
<SliderControl
62-
:label="t('maskEditor.opacity')"
63-
:min="0"
64-
:max="1"
65-
:step="0.01"
66-
:model-value="store.brushSettings.opacity"
67-
@update:model-value="onOpacityChange"
68-
/>
69-
70-
<SliderControl
71-
:label="t('maskEditor.hardness')"
72-
:min="0"
73-
:max="1"
74-
:step="0.01"
75-
:model-value="store.brushSettings.hardness"
76-
@update:model-value="onHardnessChange"
77-
/>
78-
79-
<SliderControl
80-
:label="$t('maskEditor.stepSize')"
81-
:min="1"
82-
:max="100"
83-
:step="1"
84-
:model-value="store.brushSettings.stepSize"
85-
@update:model-value="onStepSizeChange"
86-
/>
134+
<!-- Step Size -->
135+
<div class="flex flex-col gap-2">
136+
<div class="flex items-center justify-between">
137+
<span class="text-left text-xs font-sans text-descrip-text">
138+
{{ t('maskEditor.stepSize') }}
139+
</span>
140+
<input
141+
v-model.number="brushStepSize"
142+
type="number"
143+
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"
144+
:min="1"
145+
:max="100"
146+
:step="1"
147+
/>
148+
</div>
149+
<SliderControl
150+
v-model="brushStepSize"
151+
class="flex-1"
152+
label=""
153+
:min="1"
154+
:max="100"
155+
:step="1"
156+
/>
157+
</div>
87158
</div>
88159
</template>
89160

90161
<script setup lang="ts">
162+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
163+
91164
import { BrushShape } from '@/extensions/core/maskeditor/types'
92165
import { t } from '@/i18n'
93166
import { useMaskEditorStore } from '@/stores/maskEditorStore'
167+
import { cn } from '@/utils/tailwindUtil'
94168
95169
import SliderControl from './controls/SliderControl.vue'
96170
97171
const store = useMaskEditorStore()
98172
99-
const textButtonClass =
100-
'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'
173+
const colorInputRef = ref<HTMLInputElement>()
101174
175+
const textButtonClass =
176+
'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'
177+
178+
/* Computed properties that use store setters for validation */
179+
const brushSize = computed({
180+
get: () => store.brushSettings.size,
181+
set: (value: number) => store.setBrushSize(value)
182+
})
183+
184+
const brushOpacity = computed({
185+
get: () => store.brushSettings.opacity,
186+
set: (value: number) => store.setBrushOpacity(value)
187+
})
188+
189+
const brushHardness = computed({
190+
get: () => store.brushSettings.hardness,
191+
set: (value: number) => store.setBrushHardness(value)
192+
})
193+
194+
const brushStepSize = computed({
195+
get: () => store.brushSettings.stepSize,
196+
set: (value: number) => store.setBrushStepSize(value)
197+
})
198+
199+
/* Brush shape */
102200
const setBrushShape = (shape: BrushShape) => {
103201
store.brushSettings.type = shape
104202
}
105203
106-
const onColorChange = (event: Event) => {
107-
store.rgbColor = (event.target as HTMLInputElement).value
108-
}
109-
110-
const onThicknessChange = (value: number) => {
111-
store.setBrushSize(value)
112-
}
113-
114-
const onOpacityChange = (value: number) => {
115-
store.setBrushOpacity(value)
116-
}
117-
118-
const onHardnessChange = (value: number) => {
119-
store.setBrushHardness(value)
120-
}
121-
122-
const onStepSizeChange = (value: number) => {
123-
store.setBrushStepSize(value)
124-
}
125-
204+
/* Reset */
126205
const resetToDefault = () => {
127206
store.resetBrushToDefault()
128207
}
208+
209+
onMounted(() => {
210+
if (colorInputRef.value) {
211+
store.colorInput = colorInputRef.value
212+
}
213+
})
214+
215+
onBeforeUnmount(() => {
216+
store.colorInput = null
217+
})
129218
</script>

src/components/maskeditor/MaskEditorContent.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ onBeforeUnmount(() => {
202202
}
203203
204204
store.canvasHistory.clearStates()
205+
205206
store.resetState()
206207
dataStore.reset()
207208
})

src/extensions/core/maskeditor.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ function isOpened(): boolean {
3737
return useDialogStore().isDialogOpen('global-mask-editor')
3838
}
3939

40+
const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => {
41+
if (!isOpened()) return
42+
43+
const store = useMaskEditorStore()
44+
const oldBrushSize = store.brushSettings.size
45+
const newBrushSize = sizeChanger(oldBrushSize)
46+
store.setBrushSize(newBrushSize)
47+
}
48+
4049
app.registerExtension({
4150
name: 'Comfy.MaskEditor',
4251
settings: [
@@ -82,13 +91,24 @@ app.registerExtension({
8291
id: 'Comfy.MaskEditor.BrushSize.Increase',
8392
icon: 'pi pi-plus-circle',
8493
label: 'Increase Brush Size in MaskEditor',
85-
function: () => changeBrushSize((old) => _.clamp(old + 4, 1, 100))
94+
function: () => changeBrushSize((old) => _.clamp(old + 2, 1, 250))
8695
},
8796
{
8897
id: 'Comfy.MaskEditor.BrushSize.Decrease',
8998
icon: 'pi pi-minus-circle',
9099
label: 'Decrease Brush Size in MaskEditor',
91-
function: () => changeBrushSize((old) => _.clamp(old - 4, 1, 100))
100+
function: () => changeBrushSize((old) => _.clamp(old - 2, 1, 250))
101+
},
102+
{
103+
id: 'Comfy.MaskEditor.ColorPicker',
104+
icon: 'pi pi-palette',
105+
label: 'Open Color Picker in MaskEditor',
106+
function: () => {
107+
if (!isOpened()) return
108+
109+
const store = useMaskEditorStore()
110+
store.colorInput?.click()
111+
}
92112
}
93113
],
94114
init() {
@@ -101,12 +121,3 @@ app.registerExtension({
101121
)
102122
}
103123
})
104-
105-
const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => {
106-
if (!isOpened()) return
107-
108-
const store = useMaskEditorStore()
109-
const oldBrushSize = store.brushSettings.size
110-
const newBrushSize = sizeChanger(oldBrushSize)
111-
store.setBrushSize(newBrushSize)
112-
}

src/stores/maskEditorStore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export const useMaskEditorStore = defineStore('maskEditor', () => {
7373

7474
const tgpuRoot = ref<any>(null)
7575

76+
const colorInput = ref<HTMLInputElement | null>(null)
77+
7678
watch(maskCanvas, (canvas) => {
7779
if (canvas) {
7880
maskCtx.value = canvas.getContext('2d', { willReadFrequently: true })
@@ -113,7 +115,7 @@ export const useMaskEditorStore = defineStore('maskEditor', () => {
113115
})
114116

115117
function setBrushSize(size: number): void {
116-
brushSettings.value.size = _.clamp(size, 1, 500)
118+
brushSettings.value.size = _.clamp(size, 1, 250)
117119
}
118120

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

253255
tgpuRoot,
254256

257+
colorInput,
258+
255259
setBrushSize,
256260
setBrushOpacity,
257261
setBrushHardness,

0 commit comments

Comments
 (0)