aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
-rw-r--r--public/bluebey-studio/src/panel.js70
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(() => {