diff options
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
| -rw-r--r-- | public/bluebey-studio/src/panel.js | 73 |
1 files changed, 51 insertions, 22 deletions
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js index 63e501b..237d6c8 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -28,7 +28,8 @@ const SECTION_ICONS = { '目': 'M2.5 12s3.6-6 9.5-6 9.5 6 9.5 6-3.6 6-9.5 6-9.5-6-9.5-6ZM12 14.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2Z', '眉': 'M4 11q3.2-3.2 7-2M13 9q3.8-1.2 7 2', '眼鏡・サングラス': 'M8 10.2a3.2 3.2 0 1 0 0 6.4 3.2 3.2 0 0 0 0-6.4ZM16 10.2a3.2 3.2 0 1 0 0 6.4 3.2 3.2 0 0 0 0-6.4ZM11.2 13h1.6M4.8 11.7 3 10.3M19.2 11.7 21 10.3', - 'ほっぺ・頭の模様': 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM9.4 10.2h.01M14.6 10.2h.01M9.4 14.2h5.2', + '髪': 'M4 14c0-4.8 3.6-7.6 8-7.6s8 2.8 8 7.6M4 14c2.6-1.4 5.3-1.9 8-1.8M20 14c-2.6-1.4-5.3-1.9-8-1.8', + 'ほっぺ': 'M6.4 14.6a2 1.4 0 1 0 0 .01ZM17.6 14.6a2 1.4 0 1 0 0 .01ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Z', '鼻ちょうちん': 'M15 4.6a4.6 4.6 0 1 0 0 9.2 4.6 4.6 0 0 0 0-9.2ZM10.6 9.6 4.8 11.4', 'ひげ': 'M4.4 11.2c2.6-1.2 5.2-.6 7.6 2 2.4-2.6 5-3.2 7.6-2', '口': 'M4.6 10c3 4.8 11.8 4.8 14.8 0', @@ -169,7 +170,7 @@ export function buildPanel(app, root) { right: randomEye(), cheeks: { enabled: Math.random() < 0.5 }, beard: { shape: beardShape, ...(BEARD_KIND_DEFAULTS[beardShape] ?? { size: 1, offsetY: 0, spacing: 0 }) }, - headMark: { shape: pick(['off', 'cap', 'fringe', 'fringe-up', 'wave', 'side']) }, + headMark: { shape: pick(['off', 'cap', 'fringe', 'fringe-up', 'curve', 'side']) }, brow: { enabled: true, angle: Math.round(rnd(-30, 40)), @@ -390,6 +391,21 @@ export function buildPanel(app, root) { let faceSection = section(tabFace, 'プリセット'); + // The face tab's sections are created here in the order they should read on + // screen (プリセット -> 髪 -> 眉 -> 目 -> 見る先 -> ...), but filled further + // down where the widgets are built: the DOM order is fixed at creation. + const faceTabSections = { + hair: section(tabFace, '髪'), + brow: section(tabFace, '眉'), + eye: section(tabFace, '目'), + look: section(tabFace, '見る先'), + glasses: section(tabFace, '眼鏡・サングラス'), + cheeks: section(tabFace, 'ほっぺ'), + snot: section(tabFace, '鼻ちょうちん'), + beard: section(tabFace, 'ひげ'), + mouth: section(tabFace, '口'), + }; + faceSection.add(buttons({ items: FACE_PRESETS.map((preset) => ({ id: preset.id, @@ -408,7 +424,7 @@ export function buildPanel(app, root) { } // --- eyes - faceSection = section(tabFace, '目'); + faceSection = faceTabSections.eye; const eyeWidgets = {}; // The link switch sits directly above the *left* eye's open slider, because that @@ -748,7 +764,7 @@ export function buildPanel(app, root) { })); // --- eyebrows (the original character has none; this is extra range) - faceSection = section(tabFace, '眉'); + faceSection = faceTabSections.brow; faceSection.add(hint('ぶるべーには眉がありません。怒った顔など、必要なときだけ出してください。')); const browToggle = check({ @@ -859,7 +875,7 @@ export function buildPanel(app, root) { })); // --- glasses / sunglasses (drawn into the same texture as the eyes and brows) - faceSection = section(tabFace, '眼鏡・サングラス'); + faceSection = faceTabSections.glasses; faceSection.add(hint('眼鏡やサングラスを、目や眉と同じ絵にかけます。' + '線画のときはレンズを塗らず、フレームの線だけになります。')); @@ -988,11 +1004,11 @@ export function buildPanel(app, root) { ], })); - // --- ほっぺ (a manga blush) and 頭の模様 (a mark on the head) - faceSection = section(tabFace, 'ほっぺ・頭の模様'); - faceSection.add(hint('ほっぺ(マンガの赤らみ)と、頭の模様を、目や眉と同じ絵にかけます。' - + 'どちらも決まったキャラクターの絵ではなく、よくあるマンガの記号として用意しました。' - + 'はじめは両方とも出ていません。')); + // --- ほっぺ (a manga blush) + faceSection = faceTabSections.cheeks; + faceSection.add(hint('ほっぺ(マンガの赤らみ)を、目や眉と同じ絵にかけます。' + + '決まったキャラクターの絵ではなく、よくあるマンガの記号として用意しました。' + + 'はじめは出ていません。')); const cheeksToggle = check({ label: 'ほっぺを出す', @@ -1028,14 +1044,19 @@ export function buildPanel(app, root) { cheekBox.add(hint('線はいつも4本です。「よこの間隔」は左右のほっぺのへだたり、' + '「たかさ」は目からの下がり具合です。')); + // --- 髪 (a hair-like covering over the whole head; its own section, above 眉) + faceSection = faceTabSections.hair; + faceSection.add(hint('髪を頭にかぶせます。決まったキャラクターの髪ではなく、' + + 'よくあるマンガの形として用意しました。はじめは出ていません。')); + const headMarkShape = segmented({ - label: '頭の模様', + label: '髪', options: [ { value: 'off', label: 'なし' }, { value: 'cap', label: 'かぶせ' }, { value: 'fringe', label: 'まえがみ' }, - { value: 'fringe-up', label: 'まえがみ(逆)' }, - { value: 'wave', label: 'なみ' }, + { value: 'fringe-up', label: 'はちわれ' }, + { value: 'curve', label: 'カーブ' }, { value: 'side', label: 'わけめ' }, ], value: 'off', @@ -1043,6 +1064,13 @@ export function buildPanel(app, root) { }); faceSection.add(headMarkShape.el); + const headMarkOverEyes = check({ + label: '髪を目の前に出す', + value: true, + onChange: (value) => { state.face.eyes.headMark.overEyes = value; app.actions.refresh('face'); }, + }); + faceSection.add(controlRow(null, headMarkOverEyes.el, { wide: true })); + const headMarkSliders = { size: slider({ label: '大きさ', min: 0.05, max: 2.2, step: 0.01, value: 1, format: (v) => v.toFixed(2), @@ -1062,7 +1090,7 @@ export function buildPanel(app, root) { headMarkBox.add(headMarkSliders.offsetX.el); headMarkBox.add(headMarkSliders.offsetY.el); const headMarkColor = colorField({ - label: '模様の色', value: '#8a4fe0', + label: '髪の色', value: '#8a4fe0', swatches: ['#8a4fe0', '#2a1e33', '#e0344f', '#7a5c2e'], onChange: (v) => { state.face.eyes.headMark.color = v; app.actions.refresh('face'); }, }); @@ -1073,13 +1101,13 @@ export function buildPanel(app, root) { }); headMarkBox.add(headMarkColor.el); headMarkBox.add(headMarkColor2.el); - headMarkBox.add(hint('「頭の模様」は、頭にかぶせる髪のような塗りです。' - + '「まえがみ(逆)」は、まえがみを上下さかさまにした形です。' - + '「わけめ」は、髪を分けた筋が入ります。' - + '決まったキャラクターの髪ではなく、よくあるマンガの記号として用意しました。')); + headMarkBox.add(hint('「かぶせ」は頭にかぶせる丸い髪、「まえがみ」は中央がとがった前髪、' + + '「はちわれ」は中央が逆に持ち上がった生え際、「カーブ」はドラえもんのように単純にカーブした髪、' + + '「わけめ」は7:3に分けた髪です。「たかさ」で生え際を目の中心あたりまで下げられます。' + + '「髪を目の前に出す」を切ると、髪が目の後ろに回ります。')); // --- 鼻ちょうちん: the snot bubble of a sleeping face. - faceSection = section(tabFace, '鼻ちょうちん'); + faceSection = faceTabSections.snot; const snotToggle = check({ label: '鼻ちょうちんを出す', value: false, @@ -1118,7 +1146,7 @@ export function buildPanel(app, root) { + '泡は鼻の横に出るので、位置は「よこの位置」「たかさ」で調整できます。')); // --- ひげ: mustaches and beards, hung under the nose. - faceSection = section(tabFace, 'ひげ'); + faceSection = faceTabSections.beard; const beardShape = segmented({ label: 'ひげ', options: [ @@ -1179,7 +1207,7 @@ export function buildPanel(app, root) { + '「左右の間隔」「瓢の長さ」「線の間隔」は「ねこ」で使います。')); // --- mouth - faceSection = section(tabFace, '口'); + faceSection = faceTabSections.mouth; const mouthToggle = check({ label: '口を表示', @@ -1483,7 +1511,7 @@ export function buildPanel(app, root) { + '「シーン」タブの「セリフ(吹き出し)」の文とは別で、こちらは長さにだけ使います。')); // --- 見る先 (moved here, next to the face controls, from the pose tab) ---- - const lookAtSection = section(tabFace, '見る先'); + const lookAtSection = faceTabSections.look; // `lookAt` moves the eyes and the face only, so the face has to be rebuilt too. const refreshLookAt = () => { app.actions.refresh('lookAt'); @@ -2684,6 +2712,7 @@ export function buildPanel(app, root) { for (const [key, widget] of Object.entries(cheekSliders)) widget.set(state.face.eyes.cheeks[key]); cheekColor.set(state.face.eyes.cheeks.color); headMarkShape.set(state.face.eyes.headMark.shape ?? 'off'); + headMarkOverEyes.set(state.face.eyes.headMark.overEyes !== false); for (const [key, widget] of Object.entries(headMarkSliders)) widget.set(state.face.eyes.headMark[key]); headMarkColor.set(state.face.eyes.headMark.color); headMarkColor2.set(state.face.eyes.headMark.color2 ?? '#ffffff'); |
