diff options
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
| -rw-r--r-- | public/bluebey-studio/src/panel.js | 70 |
1 files changed, 26 insertions, 44 deletions
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js index 5ef2b9c..b8f1d63 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -427,6 +427,8 @@ export function buildPanel(app, root) { faceSection = faceTabSections.eye; const eyeWidgets = {}; + /** Tear sliders, which are added to the こまかい設定 box once it exists. */ + const tearWidgets = []; // The link switch sits directly above the *left* eye's open slider, because that // is the slider it drives - it was previously below both eyes, where it looked // like it belonged to whatever came next. @@ -572,10 +574,8 @@ export function buildPanel(app, root) { faceSection.add(closedLines.el); faceSection.add(threeFlip.el); faceSection.add(eyeX.el); - faceSection.add(tear.el); - faceSection.add(tearY.el); - faceSection.add(tearX.el); - faceSection.add(tearTilt.el); + // The tears go into the こまかい設定 box later, with the other extras. + tearWidgets.push(tear.el, tearY.el, tearX.el, tearTilt.el); } // One button to put every eye setting back to the artwork's defaults. The @@ -663,10 +663,17 @@ export function buildPanel(app, root) { onInput: (value) => { state.face.eyes.lidTilt = value; app.actions.refresh('face'); }, }); faceSection.add(lidTiltSlider.el); + const hideWhiteToggle = check({ + label: '白目を消す', + value: false, + onChange: (value) => { state.face.eyes.hideWhite = value; app.actions.refresh('face'); }, + }); + faceSection.add(controlRow(null, hideWhiteToggle.el, { wide: true })); faceSection.add(hint('「まぶたの形」を「直線」にすると、まぶたが平らに下りてきます。' - + '「まぶたの傾き」で、細めた目を左右反転で傾けられます。')); + + '「まぶたの傾き」で、細めた目を左右反転で傾けられます。' + + '「白目を消す」は、細めた目やハートのときに白目を出しません。')); - const eyeAdvanced = details(faceSection.body, 'こまかい設定(瞳・ハート・涙・まぶた)'); + const eyeAdvanced = details(faceSection.body, 'こまかい設定(瞳・色・ハート・まぶた・涙・手描き)'); const irisScaleSlider = slider({ label: '瞳の大きさ', min: 0.4, max: 1.4, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: (value) => { state.face.eyes.irisScale = value; app.actions.refresh('face'); }, @@ -722,6 +729,10 @@ export function buildPanel(app, root) { const eyeColorBox = details(faceSection.body, '色(白目・瞳・まぶたの線)'); for (const widget of Object.values(eyeColors)) eyeColorBox.add(widget.el); + // Tears (per eye) live here with the other extras. + const tearBox = details(eyeAdvanced.body, '涙(左右べつ)'); + for (const el of tearWidgets) tearBox.add(el); + const eyeSource = selectField({ label: '目のパーツ', // 実際にモデルに入っている手描きパーツだけを一覧に出す(消したものが残ると混乱するため) @@ -733,7 +744,7 @@ export function buildPanel(app, root) { value: 'parametric', onChange: (value) => { state.face.eyes.source = value; app.actions.refresh('face'); }, }); - faceSection.add(eyeSource.el); + eyeAdvanced.add(eyeSource.el); // 手描き画像の読み込み(GIMP などで描いた PNG をそのまま使えます) const rebuildEyeOptions = () => { @@ -770,8 +781,8 @@ export function buildPanel(app, root) { toast('画像を読み込めませんでした(PNG / JPG をお使いください)'); } }); - faceSection.add(eyeImagePicker); - faceSection.add(buttons({ + eyeAdvanced.add(eyeImagePicker); + eyeAdvanced.add(buttons({ items: [{ id: 'eyeimg', label: '目の画像を読み込む(PNG)', onClick: () => { if (confirmImageLicense()) eyeImagePicker.click(); } }], })); @@ -1257,10 +1268,12 @@ export function buildPanel(app, root) { corners: slider({ label: '口角の長さ', min: 0, max: 1.6, step: 0.01, value: 0.71, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.corners = v; app.actions.refresh('face'); } }), cornerAngle: slider({ label: '口角の角度', min: -70, max: 70, step: 1, value: -8, format: (v) => `${Math.round(v)}°`, onInput: (v) => { state.face.mouth.cornerAngle = v; app.actions.refresh('face'); } }), }; - const mouthVisible = ['smile', 'round', 'width', 'length', 'thickness', 'tilt', 'offsetY']; + const mouthVisible = ['smile', 'round', 'length', 'thickness', 'tilt', 'offsetY']; for (const key of mouthVisible) faceSection.add(mouthSliders[key].el); - const mouthBox = details(faceSection.body, 'こまかい設定(開き・舌・口角)'); - for (const key of ['open', 'tongue', 'tonguePos', 'corners', 'cornerAngle']) mouthBox.add(mouthSliders[key].el); + faceSection.add(hint('「口の長さ」は口の横の長さです。「口の幅」は丸い口(O)の横の広がりで、' + + 'こまかい設定にあります。')); + const mouthBox = details(faceSection.body, 'こまかい設定(幅・開き・舌・口角)'); + for (const key of ['width', 'open', 'tongue', 'tonguePos', 'corners', 'cornerAngle']) mouthBox.add(mouthSliders[key].el); mouthBox.add(buttons({ items: [{ id: 'mouthreset', @@ -1282,37 +1295,6 @@ export function buildPanel(app, root) { const mouthColorBox = details(faceSection.body, '色(口の線・中・舌・口角)'); for (const widget of Object.values(mouthColors)) mouthColorBox.add(widget.el); - const mouthSource = selectField({ - label: '口のパーツ', - options: [ - { value: 'parametric', label: 'パラメータで作る(おすすめ)' }, - { value: 'original', label: '手描き(原本)' }, - ], - value: 'parametric', - onChange: (value) => { state.face.mouth.source = value; app.actions.refresh('face'); }, - }); - faceSection.add(mouthSource.el); - - const mouthImagePicker = h('input', { type: 'file', accept: 'image/*', style: { display: 'none' } }); - mouthImagePicker.addEventListener('change', async () => { - const file = mouthImagePicker.files?.[0]; - mouthImagePicker.value = ''; - if (!file) return; - try { - await app.face.loadImage('mouth', file); - state.face.mouth.source = 'original'; - sync(); - toast(`口の画像「${file.name}」を読み込みました`); - } catch (error) { - console.error(error); - toast('画像を読み込めませんでした(PNG / JPG をお使いください)'); - } - }); - faceSection.add(mouthImagePicker); - faceSection.add(buttons({ - items: [{ id: 'mouthimg', label: '口の画像を読み込む(PNG)', onClick: () => { if (confirmImageLicense()) mouthImagePicker.click(); } }], - })); - /* ---------------------------------------------------------------- caption */ const captionSection = section(tabScene, 'セリフ(吹き出し)'); @@ -2711,6 +2693,7 @@ export function buildPanel(app, root) { lowerLidSlider.set(state.face.eyes.lowerLid ?? 0); lidShapeSegment.set(state.face.eyes.lidShape ?? 'curve'); lidTiltSlider.set(state.face.eyes.lidTilt ?? 0); + hideWhiteToggle.set(state.face.eyes.hideWhite === true); browToggle.set(state.face.eyes.brow.enabled); browSliders.angle.set(state.face.eyes.brow.angle); browSliders.height.set(state.face.eyes.brow.height); @@ -2756,7 +2739,6 @@ export function buildPanel(app, root) { mouthToggle.set(state.face.mouth.visible); for (const [key, widget] of Object.entries(mouthSliders)) widget.set(state.face.mouth[key]); for (const [key, widget] of Object.entries(mouthColors)) widget.set(state.face.mouth[key]); - mouthSource.set(state.face.mouth.source); }); syncers.push(() => { |
