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.js52
1 files changed, 38 insertions, 14 deletions
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js
index e9764c1..4b2a4c0 100644
--- a/public/bluebey-studio/src/panel.js
+++ b/public/bluebey-studio/src/panel.js
@@ -10,7 +10,7 @@ import { STYLE_DEFS } from './styles.js';
import { EYE_VARIANTS } from './model.js';
import { ENVIRONMENTS } from './look.js';
import { BACKGROUND_PRESETS, EFFECT_PRESETS } from './background.js';
-import { PROP_LIBRARY, applyPropFaceScale } from './props.js';
+import { PROP_LIBRARY, applyPropFaceScale, applyPropText } from './props.js';
import {
GION_SHEETS, gionSheetUrl, cropFromMarquee, fitSheet, normalizeRect, clamp, DEFAULT_STAMP_WIDTH,
} from './gion.js';
@@ -244,9 +244,9 @@ export function buildPanel(app, root) {
// こまかい設定: the whole-body nudge and the rotation gizmo are tweaks, not the
// main way in, so they stay folded away inside the bone section.
const rootSliders = [
- { label: 'よこ移動', min: -5, max: 5 },
- { label: 'たて移動', min: -2, max: 6 },
- { label: 'おくゆき', min: -5, max: 5 },
+ { label: 'よこ移動', min: -20, max: 20 },
+ { label: 'たて移動', min: -10, max: 20 },
+ { label: 'おくゆき', min: -20, max: 20 },
].map(({ label, min, max }, index) => slider({
label, min, max, step: 0.01, value: 0,
format: (v) => v.toFixed(2),
@@ -1526,7 +1526,15 @@ export function buildPanel(app, root) {
onChange: (value) => { state.render.contactShadow = value; app.actions.refresh('render'); },
});
lookSection.add(controlRow(null, contactShadowToggle.el, { wide: true }));
- lookSection.add(hint('影の向きは「ひかり」の光の向き(パッド)にしたがって変わります。「接地影だけ」は足もとの丸い影だけにします。'));
+ const contactBlobToggle = check({
+ label: '足もとの丸い影',
+ value: false,
+ onChange: (value) => { state.render.contactBlob = value; app.actions.refresh('render'); },
+ });
+ lookSection.add(controlRow(null, contactBlobToggle.el, { wide: true }));
+ lookSection.add(hint('影の向きは「ひかり」の光の向き(パッド)にしたがって変わります。'
+ + '「接地影だけ」は足もとの丸い影だけにします。'
+ + '「足もとの丸い影」は、足もとに薄く敷く丸い影です(はじめは出ていません)。'));
const environmentSegment = segmented({
label: '照明の種類',
@@ -1721,10 +1729,10 @@ export function buildPanel(app, root) {
/* ------------------------------------------------------------------- props */
- // 舞台: the things you put the character *among* - the props and the wall - in
- // one place, since they are set up together.
- const stageSection = section(tabScene, '舞台');
- const propSection = details(stageSection.body, '小物');
+ // 小物: the things you put the character *among*. Each stage feature gets its
+ // own top-level section rather than one shared 舞台 drawer, so they are easier
+ // to find and open independently.
+ const propSection = section(tabScene, '小物');
propSection.add(buttons({
label: '追加',
items: PROP_LIBRARY.map((entry) => ({
@@ -1780,7 +1788,21 @@ export function buildPanel(app, root) {
// state and carries only the whole-prop scale, so the props syncer
// re-applies this to the fresh mesh (see `applyPropFaceScale`).
let faceScale = null;
+ let signText = null;
if (item.kind === 'sign') {
+ signText = textArea({
+ label: '看板の文字(改行できます)',
+ value: '',
+ onChange: (value) => {
+ const target = state.props?.items?.[index];
+ if (target) target.text = value;
+ // Paint straight onto the live face rather than rebuilding every prop
+ // on each keystroke; a later rebuild re-applies it (see main.js).
+ applyPropText(placedPropGroups(app)[index], value);
+ app.needsRender = true;
+ },
+ });
+ block.insertBefore(signText.el, numbers.el);
faceScale = slider({
label: '文字部分の大きさ', min: 0.4, max: 2.4, step: 0.01, value: 1,
format: (v) => v.toFixed(2), onInput: patch('faceScale'),
@@ -1801,7 +1823,7 @@ export function buildPanel(app, root) {
}],
}).el);
propListBox.append(block);
- propWidgets.push({ index, sliders, faceScale });
+ propWidgets.push({ index, sliders, faceScale, signText });
});
};
renderProps();
@@ -1812,7 +1834,7 @@ export function buildPanel(app, root) {
// so the user picks the crop with a marquee instead of the app slicing them
// (see src/gion.js for why). Only the sheet name and the crop rectangle live in
// the state - never the bitmap - so a shared link stays small.
- const gionSection = details(stageSection.body, '擬音(マンガのオノマトペ)');
+ const gionSection = section(tabScene, '擬音(マンガのオノマトペ)');
gionSection.add(hint('擬音(ドドド、バン!など)の画像から、四角く切り出したスタンプを画面に貼れます。'
+ '「擬音をえらぶ」で画像を開き、ドラッグで四角を描いて「追加」を押してください。'
+ '貼ったあとはビューポートでドラッグして動かせます。'));
@@ -2069,7 +2091,7 @@ export function buildPanel(app, root) {
// 見えない壁: place up to two finite "boards" and bury the character in them, so
// only the part in front still shows. A wall's position is kept *relative to the
// character*, so moving ぶるべー carries its walls along (see src/clip.js).
- const wallSection = details(stageSection.body, '見えない壁');
+ const wallSection = section(tabScene, '見えない壁');
wallSection.add(hint('見えない板(有限の壁)を置くと、その向こう側が隠れます。'
+ 'ぶるべーを壁に埋め込むと、体の一部だけが見えるようになります。'
+ '板は2枚まで置けます。板の位置はぶるべーからの相対なので、ぶるべーを動かすと壁も一緒に動きます。'
@@ -2443,8 +2465,8 @@ export function buildPanel(app, root) {
// order the user meets them: what is behind the character, what is beside it,
// the speech bubble, then the light and the read-out.
tabScene.append(
- backdropSection.el, stageSection.el, captionSection.el,
- screenLightSection.el, infoSection.el,
+ backdropSection.el, propSection.el, gionSection.el, wallSection.el,
+ captionSection.el, screenLightSection.el, infoSection.el,
);
boneSyncers.push(() => {
@@ -2584,6 +2606,7 @@ export function buildPanel(app, root) {
shadowOpacitySlider.set(state.render?.shadowOpacity ?? 0.22);
shadowSoftnessSlider.set(state.render?.shadowSoftness ?? 1.6);
contactShadowToggle.set(state.render?.contactShadow === true);
+ contactBlobToggle.set(state.render?.contactBlob === true);
environmentSegment.set(state.render?.environment ?? 'gradient');
});
@@ -2618,6 +2641,7 @@ export function buildPanel(app, root) {
entry.sliders.scale.set(item.scale ?? 1);
entry.faceScale?.set(item.faceScale ?? 1);
applyPropFaceScale(groups[entry.index], item.faceScale);
+ entry.signText?.set(item.text ?? '');
}
});