danoniplus 47.5.3 → 47.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/danoni_main.js CHANGED
@@ -4,12 +4,12 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2026/05/09
7
+ * Revised : 2026/05/12
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 47.5.3`;
12
- const g_revisedDate = `2026/05/09`;
11
+ const g_version = `Ver 47.6.0`;
12
+ const g_revisedDate = `2026/05/12`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -10194,6 +10194,9 @@ const keyConfigInit = (_kcType = g_kcType) => {
10194
10194
  // カーソル位置の初期化
10195
10195
  appearConfigSteps(g_keycons.keySwitchNum);
10196
10196
 
10197
+ keyconfigKeyboardPreview.dispose();
10198
+ keyconfigKeyboardPreview.init(divRoot);
10199
+
10197
10200
  // ラベル・ボタン描画
10198
10201
  multiAppend(divRoot,
10199
10202
 
@@ -10342,6 +10345,652 @@ const keyConfigInit = (_kcType = g_kcType) => {
10342
10345
  document.oncontextmenu = () => false;
10343
10346
  };
10344
10347
 
10348
+ /**
10349
+ * キーボードレイアウトプレビュー(Canvas版)
10350
+ *
10351
+ * キーコンフィグ画面(divRoot)配下に以下の要素を追加する:
10352
+ * - [Preview] ボタン (createCss2Button)
10353
+ * - プレビュー用コンテナ div (createEmptySprite)
10354
+ * ├ キーボード背景 canvas (_state.canvasBase) ← 起動時のみ描画
10355
+ * └ マッピング強調 canvas (_state.canvasMap) ← キー変更・表示時に再描画
10356
+ *
10357
+ * 前提:
10358
+ * - g_kCd : グローバル定義済み。ロケール切替後は g_lang_kCd がマージ済み。
10359
+ * 未設定キーは空文字列 `""` で初期化。
10360
+ * 右Shift/Ctrl/Alt は 256/257/258 の独自コードで定義。
10361
+ * - g_btnWidth() : 画面の横幅(px)を返すグローバル関数。
10362
+ * - g_sHeight : 画面の縦幅(px)を保持するグローバル変数。
10363
+ * - createCss2Button, createEmptySprite : danoniplus 本体のグローバル関数。
10364
+ *
10365
+ * 使い方:
10366
+ * 【初期化時】
10367
+ * keyconfigKeyboardPreview.init(divRoot);
10368
+ *
10369
+ * 【キー割り当てが変わった時】
10370
+ * keyconfigKeyboardPreview.refresh();
10371
+ * ※ g_keyObj.currentKey / currentPtn から自動取得する
10372
+ *
10373
+ * 【キーコンフィグ画面を離脱する時】
10374
+ * keyconfigKeyboardPreview.dispose();
10375
+ */
10376
+
10377
+ const keyconfigKeyboardPreview = (() => {
10378
+
10379
+ // -------------------------------------------------------------------------
10380
+ // 定数
10381
+ // -------------------------------------------------------------------------
10382
+ const C_PREVIEW_ID = `kbPreviewArea`;
10383
+ const C_BTN_ID = `btnKbPreview`;
10384
+ const C_CANVAS_BASE_ID = `kbCanvasBase`;
10385
+ const C_CANVAS_MAP_ID = `kbCanvasMap`;
10386
+
10387
+ // 色定義(既存ゲームの配色に合わせたダーク系)
10388
+ const C_COLOR = {
10389
+ keyFill: `#1a1a2e`, // 通常キー背景
10390
+ keyStroke: `#555577`, // 通常キー枠
10391
+ keyText: `#ccccdd`, // 通常キー文字
10392
+ keySubText: `#888899`, // サブラベル(Shift面)
10393
+ mappedFill: `#003366`, // メインキー背景
10394
+ mappedStroke: `#4488ff`, // メインキー枠
10395
+ mappedText: `#aaddff`, // メインキー文字
10396
+ altFill: `#3e3e1a`, // 代替キー背景
10397
+ altStroke: `#777755`, // 代替キー枠
10398
+ altText: `#eeeecc`, // 代替キー文字
10399
+ bgFill: `#0d0d1a`, // Canvas 背景
10400
+ legendText: `#888899`, // 凡例テキスト
10401
+ };
10402
+
10403
+ // ボタン配置
10404
+ // X・W は init() 内で動的計算(g_btnWidth() 依存)
10405
+ // Y: KeyConfig テキスト上の余白に収まるよう小さく設定
10406
+ const BTN_H = 18; // ボタン高さ
10407
+ const BTN_Y = 3; // divRoot 内 Y 座標
10408
+ const BTN_FS = 11; // ボタンのフォントサイズ(px)
10409
+
10410
+ // プレビューエリア
10411
+ // X: canvas を divRoot 内で水平センタリング(init で動的計算)
10412
+ // Y: g_sHeight に対して垂直センタリング(init で動的計算)
10413
+
10414
+ // 凡例エリアの高さ
10415
+ const LEGEND_H = 25;
10416
+
10417
+ // -------------------------------------------------------------------------
10418
+ // キーレイアウト定義
10419
+ //
10420
+ // 各行: { offsetX, keys }
10421
+ // offsetX : 行左端の水平オフセット(単位: BASE_KEY_W)。正の値 = 右にずらす。
10422
+ // keys : キー定義の配列
10423
+ //
10424
+ // 各キー: { kc, w, h?, label? }
10425
+ // kc : keyCode(数値)。-1 はスペーサー(描画・キャッシュなし)。
10426
+ // w : 幅倍率(BASE_KEY_W 基準。省略時 1)
10427
+ // h : 高さ倍率(省略時 1)
10428
+ // label : 省略時は g_kCd[kc] を参照。g_kCd が空文字のキーや
10429
+ // 左右を区別したいキーに指定する。
10430
+ //
10431
+ // 右Shift/Ctrl/Alt は danoniplus 独自コード 256〜258 を使用。
10432
+ // -------------------------------------------------------------------------
10433
+
10434
+ // メインキーボード部(Fn行 + 数字行 + QWERTY + ASDF + ZXCV + スペース行)
10435
+ //
10436
+ // 各行の offsetX 設計(1u = BASE_KEY_W):
10437
+ // Fn行 0u Esc が左端
10438
+ // 数字行 0u 229(IME/`) が左端
10439
+ // QWERTY 0u Tab(1.5u) が左端、左端位置は数字行と揃う
10440
+ // ASDF 0u CapsLk を 2.25u にして L)Shift 左端と揃える
10441
+ // ZXCV 0.25u 標準キーボードの行オフセットを offsetX + L)Shift 拡大で再現
10442
+ // スペース行 0u
10443
+ //
10444
+ // JIS と US の主な違い:
10445
+ // 数字行: JIS は intlYen(220) あり、US はなし(BackSpace が広い)
10446
+ // QWERTY: JIS は [ の右にスペーサー、US はなし(Enter が横長)
10447
+ // ASDF : JIS は ¥(221) あり、US はなし(Enter が横長)
10448
+ // ZXCV : JIS は intlRo(226) あり、US はなし(R)Shift が広い)
10449
+ // Enter : JIS は縦長(h=2)、US は横長(h=1, w=2.25)
10450
+ /**
10451
+ * g_localeObj.val に応じた MAIN_ROWS を生成して返す。
10452
+ * drawBase / calcScale の都度呼び出し、locale 変化を反映する。
10453
+ *
10454
+ * @returns {Array} MAIN_ROWS 相当の配列
10455
+ */
10456
+ const buildMainRows = () => {
10457
+ const isJa = g_localeObj.val === `Ja`;
10458
+ return [
10459
+ // Row0: Fn キー行(JIS/US 共通)
10460
+ {
10461
+ offsetX: 0,
10462
+ keys: [
10463
+ { kc: 27 }, // Esc
10464
+ { kc: -1, w: 0.5 }, // スペーサー
10465
+ { kc: 112 }, { kc: 113 }, { kc: 114 }, { kc: 115 },
10466
+ { kc: -1, w: 0.25 }, // スペーサー
10467
+ { kc: 116 }, { kc: 117 }, { kc: 118 }, { kc: 119 },
10468
+ { kc: -1, w: 0.25 }, // スペーサー
10469
+ { kc: 120 }, { kc: 121 }, { kc: 122 }, { kc: 123 },
10470
+ ],
10471
+ },
10472
+ // Row1: 数字行
10473
+ // JIS: ..., 222, 220(intlYen, 0.75u), BS(1.5u)
10474
+ // US : ..., 222, BS(2.25u)
10475
+ {
10476
+ offsetX: 0,
10477
+ keys: [
10478
+ { kc: 229 },
10479
+ { kc: 49 }, { kc: 50 }, { kc: 51 },
10480
+ { kc: 52 }, { kc: 53 }, { kc: 54 },
10481
+ { kc: 55 }, { kc: 56 }, { kc: 57 },
10482
+ { kc: 48 }, { kc: 189 }, { kc: 222 },
10483
+ ...(isJa
10484
+ ? [{ kc: 220, w: 0.75 }, { kc: 8, w: 1.5 }] // JIS: intlYen + BS
10485
+ : [{ kc: 8, w: 2.25 }] // US : BS のみ(広い)
10486
+ ),
10487
+ ],
10488
+ },
10489
+ // Row2: QWERTY
10490
+ // JIS: ..., [, スペーサー(0.5u), Enter(縦長 h=2, w=1.25)
10491
+ // US : ..., [, ]
10492
+ {
10493
+ offsetX: 0,
10494
+ keys: [
10495
+ { kc: 9, w: 1.5 },
10496
+ { kc: 81 }, { kc: 87 }, { kc: 69 },
10497
+ { kc: 82 }, { kc: 84 }, { kc: 89 },
10498
+ { kc: 85 }, { kc: 73 }, { kc: 79 },
10499
+ { kc: 80 }, { kc: 192 },
10500
+ ...(isJa
10501
+ ? [{ kc: 219 }, { kc: -1, w: 0.5 }, { kc: 13, w: 1.25, h: 2 }] // JIS: [, スペーサー, Enter縦長
10502
+ : [{ kc: 219 }, { kc: 221 }] // US : [, ]
10503
+ ),
10504
+ ],
10505
+ },
10506
+ // Row3: ASDF
10507
+ // JIS: ..., L, ;, ', ¥(221)
10508
+ // US : ..., L, ;, ' ← ¥なし(Enter が下まで伸びる縦長分を吸収)
10509
+ {
10510
+ offsetX: 0,
10511
+ keys: [
10512
+ { kc: 20, w: 2.25, label: `CapsLk` },
10513
+ { kc: 65 }, { kc: 83 }, { kc: 68 },
10514
+ { kc: 70 }, { kc: 71 }, { kc: 72 },
10515
+ { kc: 74 }, { kc: 75 }, { kc: 76 },
10516
+ { kc: 187 }, { kc: 186 },
10517
+ ...(isJa
10518
+ ? [{ kc: 221 }] // JIS: ¥
10519
+ : [{ kc: 13, w: 2.25 }] // US : Enter横長
10520
+ ),
10521
+ ],
10522
+ },
10523
+ // Row4: ZXCV(標準配列は ASDF より約 0.25u 右にオフセット)
10524
+ // JIS: ..., /, intlRo(226), R)Shift(1.25u)
10525
+ // US : ..., /, R)Shift(2.5u)
10526
+ {
10527
+ offsetX: 0.25,
10528
+ keys: [
10529
+ { kc: 16, w: 2.5 },
10530
+ { kc: 90 }, { kc: 88 }, { kc: 67 },
10531
+ { kc: 86 }, { kc: 66 }, { kc: 78 },
10532
+ { kc: 77 }, { kc: 188 }, { kc: 190 },
10533
+ { kc: 191 },
10534
+ ...(isJa
10535
+ ? [{ kc: 226 }, { kc: 256, w: 1.25 }] // JIS: intlRo + R)Shift
10536
+ : [{ kc: 256, w: 2.5 }] // US : R)Shift のみ(広い)
10537
+ ),
10538
+ ],
10539
+ },
10540
+ // Row5: スペースバー行(JIS/US 共通)
10541
+ {
10542
+ offsetX: 0,
10543
+ keys: [
10544
+ { kc: 17, w: 1.25 },
10545
+ { kc: 91 },
10546
+ { kc: 18 },
10547
+ ...(isJa
10548
+ ? [
10549
+ { kc: 29 },
10550
+ { kc: 32, w: 5.25 },
10551
+ { kc: 28 },
10552
+ { kc: 242 },
10553
+ ]
10554
+ : [
10555
+ { kc: 32, w: 8.25 },
10556
+ ]
10557
+ ),
10558
+ { kc: 258 },
10559
+ { kc: 91 },
10560
+ { kc: 257, w: 1.25 },
10561
+ ],
10562
+ },
10563
+ ];
10564
+ };
10565
+ // 編集キークラスター(Insert/Delete/Home/End/PgUp/PgDn + 矢印キー)
10566
+ // MAIN_ROWS と行インデックスを揃えて配置する。空行はスキップされる。
10567
+ const NAV_ROWS = [
10568
+ { offsetX: 0, keys: [{ kc: 44, label: `PrintSc` }, { kc: 145, label: `ScrollLk` }, { kc: 19 }] }, // PrintSc ScrollLk Pause
10569
+ { offsetX: 0, keys: [{ kc: 45 }, { kc: 36 }, { kc: 33 }] }, // Insert Home PgUp
10570
+ { offsetX: 0, keys: [{ kc: 46 }, { kc: 35 }, { kc: 34 }] }, // Delete End PgDn
10571
+ { offsetX: 0, keys: [] }, // ASDF行:空
10572
+ { offsetX: 0, keys: [{ kc: -1 }, { kc: 38 }, { kc: -1 }] }, // ↑
10573
+ { offsetX: 0, keys: [{ kc: 37 }, { kc: 40 }, { kc: 39 }] }, // ← ↓ →
10574
+ ];
10575
+
10576
+ // テンキー(Space行の下に余白を空けて横に羅列)
10577
+ // kc は g_kCd 定義に従う: 96〜111=テンキー各種, 144=NumLk
10578
+ // 標準テンキーレイアウト:
10579
+ // [NumLk] [T/] [T*] [T-]
10580
+ // [T7][T8][T9] [T+]
10581
+ // [T4][T5][T6] [T+] ← T+ は縦2u
10582
+ // [T1][T2][T3] [TEnter]
10583
+ // [ T0 ][T_] [TEnter] ← T0 は横2u、TEnter は縦2u
10584
+ const NUM_ROWS = [
10585
+ { offsetX: 0, keys: [] },
10586
+ { offsetX: 0, keys: [{ kc: 144 }, { kc: 111 }, { kc: 106 }, { kc: 109 }] }, // NumLk T/ T* T-
10587
+ { offsetX: 0, keys: [{ kc: 103 }, { kc: 104 }, { kc: 105 }, { kc: 107, h: 2 }] }, // T7 T8 T9 T+(縦2u)
10588
+ { offsetX: 0, keys: [{ kc: 100 }, { kc: 101 }, { kc: 102 }] }, // T4 T5 T6
10589
+ { offsetX: 0, keys: [{ kc: 97 }, { kc: 98 }, { kc: 99 }, { kc: 108, h: 2 }] }, // T1 T2 T3 TEnter(縦2u)
10590
+ { offsetX: 0, keys: [{ kc: 96, w: 2 }, { kc: 110 }] }, // T0(横2u) T.
10591
+ ];
10592
+
10593
+ // -------------------------------------------------------------------------
10594
+ // 内部状態
10595
+ // -------------------------------------------------------------------------
10596
+ const _state = {
10597
+ visible: false,
10598
+ mappedSet: new Set(), // メインキー(各矢印の index 0)
10599
+ altSet: new Set(), // 代替キー(各矢印の index 1 以降)
10600
+ canvasBase: null,
10601
+ canvasMap: null,
10602
+ keyRects: [], // { kc, x, y, w, h, label } — drawMap で照合するキャッシュ
10603
+ scale: 1, // BASE_KEY_W/H に掛けるスケール係数
10604
+ cvsW: 500, // 実際の Canvas 幅(スケール計算後)
10605
+ cvsH: 240, // 実際の Canvas 高さ(スケール計算後)
10606
+ };
10607
+
10608
+ // -------------------------------------------------------------------------
10609
+ // スケール計算
10610
+ // -------------------------------------------------------------------------
10611
+
10612
+ /**
10613
+ * g_btnWidth() / g_sHeight を元にスケール係数と Canvas サイズを算出して
10614
+ * _state に書き込む。init 時に呼ぶ。
10615
+ *
10616
+ * 基準サイズ(フルキーボード = メイン + ナビクラスター):
10617
+ * 横: MAIN最大行幅 + ナビ幅(3キー) + 余白
10618
+ * 縦: 6行 × (BASE_KEY_H + BASE_KEY_GAP) - BASE_KEY_GAP
10619
+ * BASE_KEY_W = BASE_KEY_H = 28px、BASE_KEY_GAP = 3px を基準とする。
10620
+ */
10621
+ const BASE_KEY_W = 28;
10622
+ const BASE_KEY_H = 28;
10623
+ const BASE_KEY_GAP = 3;
10624
+ const MAIN_ROWS_LEN = 6; // MAIN_ROWS の行数(Fn行を含む)
10625
+
10626
+ // 行幅計算(スペーサー含む)
10627
+ const calcRowBaseW = row =>
10628
+ row.keys.reduce((acc, k) => acc + (k.w || 1) * BASE_KEY_W + BASE_KEY_GAP, -BASE_KEY_GAP);
10629
+
10630
+ const BASE_NAV_W = 3 * BASE_KEY_W + 2 * BASE_KEY_GAP; // NAV は 3列固定
10631
+ const BASE_ROW_H = MAIN_ROWS_LEN * (BASE_KEY_H + BASE_KEY_GAP) - BASE_KEY_GAP; // MAIN+NAV 分の高さ
10632
+ const NUM_ROWS_LEN = 6; // テンキーの行数
10633
+ const NUM_GAP_H = BASE_KEY_H * 0.4; // テンキー上部の余白(基準キー高の40%)
10634
+ const BASE_NUM_ROW_H = NUM_ROWS_LEN * (BASE_KEY_H + BASE_KEY_GAP) - BASE_KEY_GAP; // テンキー部の高さ
10635
+ const BASE_NUM_W = 4 * BASE_KEY_W + 3 * BASE_KEY_GAP; // テンキー横幅(4列固定)
10636
+
10637
+ const calcScale = () => {
10638
+ // locale に依存した行幅を毎回計算する
10639
+ const rows = buildMainRows();
10640
+ const baseMainW = Math.max(...rows.map(calcRowBaseW));
10641
+ // 横幅: メイン + NAV + テンキー + 余白
10642
+ const totalW = baseMainW + BASE_KEY_GAP * 2 + BASE_NAV_W + BASE_KEY_GAP * 2
10643
+ + BASE_KEY_GAP * 3 + BASE_NUM_W;
10644
+
10645
+ const availW = g_btnWidth();
10646
+ const availH = g_sHeight - 200 - LEGEND_H; // 下部 UI ぶんを除いた高さ
10647
+
10648
+ // 縦幅基準: MAIN/NAV 高さ と テンキー高さ(余白込み)の大きい方
10649
+ const totalH = Math.max(BASE_ROW_H, BASE_NUM_ROW_H + Math.ceil(NUM_GAP_H));
10650
+
10651
+ const scaleW = availW / totalW;
10652
+ const scaleH = availH / totalH;
10653
+ _state.scale = Math.min(scaleW, scaleH, 1.5); // 最大 1.5 倍まで拡大可
10654
+
10655
+ _state.cvsW = Math.floor(totalW * _state.scale);
10656
+ _state.cvsH = Math.floor(totalH * _state.scale) + LEGEND_H;
10657
+ };
10658
+
10659
+ // -------------------------------------------------------------------------
10660
+ // ラベル取得
10661
+ // -------------------------------------------------------------------------
10662
+
10663
+ /**
10664
+ * keyCode に対応する [primaryLabel, subLabel] を返す。
10665
+ * kc が -1(スペーサー)の場合は [``, ``] を返す。
10666
+ * g_kCd の値は `"primary"` または `"primary sub"` 形式の文字列。
10667
+ *
10668
+ * @param {number} kc
10669
+ * @param {string|undefined} forcedLabel - ROWS の label 指定がある場合に優先
10670
+ * @returns {string[]} [primary, sub]
10671
+ */
10672
+ const getKeyLabels = (kc, forcedLabel) => {
10673
+ if (kc < 0) return [``, ``];
10674
+ if (forcedLabel !== undefined) return [forcedLabel, ``];
10675
+
10676
+ const raw = g_kCd[kc];
10677
+ if (raw && raw !== `- - -` && raw !== `Unknown`) {
10678
+ const parts = raw.split(` `);
10679
+ return [parts[0] || ``, parts[1] || ``];
10680
+ }
10681
+ return [`?`, ``];
10682
+ };
10683
+
10684
+ // -------------------------------------------------------------------------
10685
+ // Canvas ヘルパー
10686
+ // -------------------------------------------------------------------------
10687
+
10688
+ // スケール済みの各寸法を返すヘルパー
10689
+ const kw = w => Math.floor(w * BASE_KEY_W * _state.scale + (w - 1) * BASE_KEY_GAP * _state.scale);
10690
+ const kh = h => Math.floor(h * BASE_KEY_H * _state.scale + (h - 1) * BASE_KEY_GAP * _state.scale);
10691
+ const kg = () => Math.max(1, Math.round(BASE_KEY_GAP * _state.scale));
10692
+ const kr = () => Math.max(2, Math.round(4 * _state.scale));
10693
+
10694
+ const roundRect = (ctx, x, y, w, h, r) => {
10695
+ ctx.beginPath();
10696
+ ctx.moveTo(x + r, y);
10697
+ ctx.lineTo(x + w - r, y);
10698
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
10699
+ ctx.lineTo(x + w, y + h - r);
10700
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
10701
+ ctx.lineTo(x + r, y + h);
10702
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
10703
+ ctx.lineTo(x, y + r);
10704
+ ctx.quadraticCurveTo(x, y, x + r, y);
10705
+ ctx.closePath();
10706
+ };
10707
+
10708
+ const drawKeyLabel = (ctx, x, y, keyW, keyH, primary, sub, textColor, subColor) => {
10709
+ const fs = primary.length >= 5
10710
+ ? Math.max(6, Math.floor(8 * _state.scale))
10711
+ : Math.max(7, Math.floor(11 * _state.scale));
10712
+
10713
+ if (sub) {
10714
+ ctx.fillStyle = subColor;
10715
+ ctx.font = `bold ${Math.max(6, Math.floor(8 * _state.scale))}px monospace`;
10716
+ ctx.textAlign = `right`;
10717
+ ctx.textBaseline = `top`;
10718
+ ctx.fillText(sub, x + keyW - 2, y + 2);
10719
+ }
10720
+ ctx.fillStyle = textColor;
10721
+ ctx.font = `bold ${fs}px monospace`;
10722
+ ctx.textAlign = `center`;
10723
+ ctx.textBaseline = `middle`;
10724
+ ctx.fillText(primary, x + keyW / 2, y + keyH / 2 + (sub ? 2 : 0));
10725
+ };
10726
+
10727
+ const drawOneKey = (ctx, x, y, keyW, keyH, fill, stroke, lw, primary, sub, textColor, subColor) => {
10728
+ roundRect(ctx, x + 0.5, y + 0.5, keyW - 1, keyH - 1, kr());
10729
+ ctx.fillStyle = fill;
10730
+ ctx.strokeStyle = stroke;
10731
+ ctx.lineWidth = lw;
10732
+ ctx.fill();
10733
+ ctx.stroke();
10734
+ drawKeyLabel(ctx, x, y, keyW, keyH, primary, sub, textColor, subColor);
10735
+ };
10736
+
10737
+ // -------------------------------------------------------------------------
10738
+ // レイアウト計算・描画
10739
+ // -------------------------------------------------------------------------
10740
+
10741
+ /**
10742
+ * rows 配列から各キーの矩形座標を計算し、
10743
+ * canvas に描画しながら keyRects へキャッシュする。
10744
+ *
10745
+ * @param {CanvasRenderingContext2D} ctx
10746
+ * @param {Array} rows - MAIN_ROWS または NAV_ROWS({offsetX, keys} 形式)
10747
+ * @param {number} originX - セクション左端の X 座標(canvas 座標)
10748
+ * @param {number} originY - セクション上端の Y 座標(canvas 座標)
10749
+ */
10750
+ const layoutSection = (ctx, rows, originX, originY) => {
10751
+ const gap = kg();
10752
+ const baseKeyH = kh(1);
10753
+
10754
+ rows.forEach((rowDef, rowIdx) => {
10755
+ if (rowDef.keys.length === 0) return;
10756
+
10757
+ const rowY = originY + rowIdx * (baseKeyH + gap);
10758
+ const startX = originX + Math.floor(rowDef.offsetX * BASE_KEY_W * _state.scale);
10759
+ let curX = startX;
10760
+
10761
+ rowDef.keys.forEach(keyDef => {
10762
+ const keyW = kw(keyDef.w || 1);
10763
+ const keyH = kh(keyDef.h || 1);
10764
+
10765
+ if (keyDef.kc >= 0) {
10766
+ _state.keyRects.push({
10767
+ kc: keyDef.kc,
10768
+ x: curX,
10769
+ y: rowY,
10770
+ w: keyW,
10771
+ h: keyH,
10772
+ label: keyDef.label,
10773
+ });
10774
+ const [primary, sub] = getKeyLabels(keyDef.kc, keyDef.label);
10775
+ drawOneKey(
10776
+ ctx, curX, rowY, keyW, keyH,
10777
+ C_COLOR.keyFill, C_COLOR.keyStroke, 1,
10778
+ primary, sub, C_COLOR.keyText, C_COLOR.keySubText
10779
+ );
10780
+ }
10781
+
10782
+ curX += keyW + gap;
10783
+ });
10784
+ });
10785
+ };
10786
+
10787
+ /**
10788
+ * キーボード背景レイヤーを描画し keyRects をキャッシュする。
10789
+ * init 時に呼ぶ。
10790
+ */
10791
+ const drawBase = () => {
10792
+ const canvas = _state.canvasBase;
10793
+ if (!canvas) return;
10794
+
10795
+ const dpr = window.devicePixelRatio || 1;
10796
+ canvas.style.top = wUnit(40);
10797
+ canvas.width = _state.cvsW * dpr;
10798
+ canvas.height = _state.cvsH * dpr;
10799
+ canvas.style.width = wUnit(_state.cvsW);
10800
+ canvas.style.height = wUnit(_state.cvsH);
10801
+
10802
+ const ctx = canvas.getContext(`2d`);
10803
+ ctx.scale(dpr, dpr);
10804
+ ctx.clearRect(0, 0, _state.cvsW, _state.cvsH);
10805
+ ctx.fillStyle = C_COLOR.bgFill;
10806
+ ctx.fillRect(0, 0, _state.cvsW, _state.cvsH);
10807
+
10808
+ _state.keyRects = [];
10809
+
10810
+ const mainRows = buildMainRows();
10811
+ const gap = kg();
10812
+ const baseMainW = Math.max(...mainRows.map(calcRowBaseW));
10813
+ const mainW = Math.floor(baseMainW * _state.scale);
10814
+ const originY = gap;
10815
+ const navOriginX = mainW + gap * 3;
10816
+
10817
+ // テンキー: NAV クラスターの右に gap*3 の余白を空けて配置
10818
+ const numOriginX = navOriginX + Math.floor(BASE_NAV_W * _state.scale) + gap * 3;
10819
+ // テンキーは MAIN 全体に対して縦方向センタリング
10820
+ const numH = Math.floor(BASE_NUM_ROW_H * _state.scale);
10821
+ const mainH = Math.floor(BASE_ROW_H * _state.scale);
10822
+ const numOriginY = originY + Math.floor((mainH - numH) / 2);
10823
+
10824
+ layoutSection(ctx, mainRows, 0, originY);
10825
+ layoutSection(ctx, NAV_ROWS, navOriginX, originY);
10826
+ layoutSection(ctx, NUM_ROWS, numOriginX, numOriginY);
10827
+
10828
+ // 凡例
10829
+ const ly = _state.cvsH - 10;
10830
+ const fnt = `${Math.max(9, Math.floor(10 * _state.scale))}px ${getBasicFont()}`;
10831
+ ctx.font = fnt;
10832
+ ctx.textAlign = `left`;
10833
+ ctx.textBaseline = `middle`;
10834
+
10835
+ const drawLegend = (x, fill, stroke, label) => {
10836
+ roundRect(ctx, x, ly, 10, 10, 2);
10837
+ ctx.fillStyle = fill; ctx.fill();
10838
+ ctx.strokeStyle = stroke; ctx.lineWidth = 1; ctx.stroke();
10839
+ ctx.fillStyle = C_COLOR.legendText;
10840
+ ctx.fillText(label, x + 14, ly + 5);
10841
+ };
10842
+
10843
+ drawLegend(8, C_COLOR.keyFill, C_COLOR.keyStroke, g_lblNameObj.unallocated);
10844
+ drawLegend(95, C_COLOR.mappedFill, C_COLOR.mappedStroke, g_lblNameObj.allocated);
10845
+ drawLegend(182, C_COLOR.altFill, C_COLOR.altStroke, g_lblNameObj.altAllocated);
10846
+ };
10847
+
10848
+ /**
10849
+ * マッピング強調レイヤーを再描画する。
10850
+ * メインキー(mappedSet)を青系、代替キー(altSet)を黄系で色分けする。
10851
+ * 同一キーにメインと代替が重なる場合はメインを優先する。
10852
+ */
10853
+ const drawMap = () => {
10854
+ const canvas = _state.canvasMap;
10855
+ if (!canvas) return;
10856
+
10857
+ const dpr = window.devicePixelRatio || 1;
10858
+ canvas.style.top = wUnit(40);
10859
+ canvas.width = _state.cvsW * dpr;
10860
+ canvas.height = _state.cvsH * dpr;
10861
+ canvas.style.width = wUnit(_state.cvsW);
10862
+ canvas.style.height = wUnit(_state.cvsH);
10863
+
10864
+ const ctx = canvas.getContext(`2d`);
10865
+ ctx.scale(dpr, dpr);
10866
+ ctx.clearRect(0, 0, _state.cvsW, _state.cvsH);
10867
+
10868
+ // 代替キーを先に描画し、メインキーで上書きすることで優先度を表現
10869
+ const drawKey = (fill, stroke, text) => rect => {
10870
+ const [primary, sub] = getKeyLabels(rect.kc, rect.label);
10871
+ roundRect(ctx, rect.x + 0.5, rect.y + 0.5, rect.w - 1, rect.h - 1, kr());
10872
+ ctx.fillStyle = fill;
10873
+ ctx.strokeStyle = stroke;
10874
+ ctx.lineWidth = 1.5;
10875
+ ctx.fill();
10876
+ ctx.stroke();
10877
+ drawKeyLabel(ctx, rect.x, rect.y, rect.w, rect.h, primary, sub, text, text);
10878
+ };
10879
+
10880
+ _state.keyRects
10881
+ .filter(rect => _state.altSet.has(rect.kc) && !_state.mappedSet.has(rect.kc))
10882
+ .forEach(drawKey(C_COLOR.altFill, C_COLOR.altStroke, C_COLOR.altText));
10883
+
10884
+ _state.keyRects
10885
+ .filter(rect => _state.mappedSet.has(rect.kc))
10886
+ .forEach(drawKey(C_COLOR.mappedFill, C_COLOR.mappedStroke, C_COLOR.mappedText));
10887
+ };
10888
+
10889
+ // -------------------------------------------------------------------------
10890
+ // 公開 API
10891
+ // -------------------------------------------------------------------------
10892
+
10893
+ /**
10894
+ * キーボードプレビューを初期化し、ボタンと Canvas コンテナを divRoot に追加する。
10895
+ *
10896
+ * @param {HTMLElement} divRoot - キーコンフィグ画面のルート div
10897
+ */
10898
+ const init = divRoot => {
10899
+ calcScale();
10900
+
10901
+ // ボタン: キャンバス横幅中央に配置、小さいサイズ
10902
+ const btnW = 80;
10903
+ const btnX = Math.floor((g_btnWidth() - btnW) / 2);
10904
+
10905
+ // "↓ Preview" → 展開、"↑ Preview" → 閉じる のトグルボタン
10906
+ const btn = createCss2Button(C_BTN_ID, `↓ Preview`, () => {
10907
+ togglePreview();
10908
+ btn.textContent = _state.visible ? `↑ Preview` : `↓ Preview`;
10909
+ }, {
10910
+ x: btnX, y: BTN_Y, w: btnW, h: BTN_H,
10911
+ title: g_msgObj.kcPreview,
10912
+ }, g_cssObj.button_Setting);
10913
+ btn.style.fontSize = `${BTN_FS}px`;
10914
+ divRoot.appendChild(btn);
10915
+
10916
+ // プレビューエリア: 水平・垂直センタリング
10917
+ const areaX = Math.floor((g_btnWidth() - _state.cvsW) / 2);
10918
+ const areaY = 130;
10919
+
10920
+ const areaDiv = createEmptySprite(divRoot, C_PREVIEW_ID, {
10921
+ x: areaX, y: areaY - 60, w: _state.cvsW, h: _state.cvsH + 80,
10922
+ pointerEvents: C_DIS_AUTO, background: `#00000080`,
10923
+ });
10924
+ areaDiv.style.display = `none`;
10925
+ areaDiv.style.position = `absolute`;
10926
+ areaDiv.style.overflow = `hidden`;
10927
+
10928
+ const canvasBase = document.createElement(`canvas`);
10929
+ canvasBase.id = C_CANVAS_BASE_ID;
10930
+ canvasBase.style.cssText = `position:absolute;left:0;top:0;`;
10931
+ areaDiv.appendChild(canvasBase);
10932
+ _state.canvasBase = canvasBase;
10933
+
10934
+ const canvasMap = document.createElement(`canvas`);
10935
+ canvasMap.id = C_CANVAS_MAP_ID;
10936
+ canvasMap.style.cssText = `position:absolute;left:0;top:0;`;
10937
+ areaDiv.appendChild(canvasMap);
10938
+ _state.canvasMap = canvasMap;
10939
+
10940
+ drawBase();
10941
+ };
10942
+
10943
+ /**
10944
+ * プレビューの表示 / 非表示を切り替える。
10945
+ * 表示するたびにマッピングレイヤーを最新化する。
10946
+ */
10947
+ const togglePreview = () => {
10948
+ const area = document.getElementById(C_PREVIEW_ID);
10949
+ if (!area) return;
10950
+
10951
+ _state.visible = !_state.visible;
10952
+ area.style.display = _state.visible ? `block` : `none`;
10953
+
10954
+ if (_state.visible) refresh();
10955
+ };
10956
+
10957
+ /**
10958
+ * g_keyObj から現在のキー割り当てを取得して mappedSet / altSet を更新し、
10959
+ * プレビューが表示中なら即時再描画する。
10960
+ *
10961
+ * g_keyObj[`keyCtrl${keyCtrlPtn}`] は二次元配列:
10962
+ * [ [矢印0のメインkeyCode, 代替keyCode, ...], [矢印1のメインkeyCode, ...], ... ]
10963
+ * 各サブ配列の index 0 がメインキー、index 1 以降が代替キー。
10964
+ */
10965
+ const refresh = () => {
10966
+ const tkObj = getKeyInfo();
10967
+ const configKeyGroupList = g_headerObj.keyGroupOrder[g_stateObj.scoreId] ??
10968
+ g_keyObj[`keyGroupOrder${tkObj.keyCtrlPtn}`] ?? tkObj.keyGroupList;
10969
+ const ctrl = g_keyObj[`keyCtrl${tkObj.keyCtrlPtn}`]
10970
+ .filter((val, idx) => tkObj.keyGroupMaps[idx].includes(configKeyGroupList[g_keycons.keySwitchNum]));
10971
+
10972
+ _state.mappedSet = new Set(ctrl.map(arr => arr[0]).filter(v => v > 0));
10973
+ _state.altSet = new Set(ctrl.flatMap(arr => arr.slice(1)).filter(v => v > 0));
10974
+ if (_state.visible) drawMap();
10975
+ };
10976
+
10977
+ /**
10978
+ * プレビューを強制非表示にしてリセットする。
10979
+ * キーコンフィグ画面の離脱時に呼ぶ。
10980
+ */
10981
+ const dispose = () => {
10982
+ _state.visible = false;
10983
+ _state.mappedSet = new Set();
10984
+ _state.altSet = new Set();
10985
+ _state.keyRects = [];
10986
+ _state.canvasBase = null;
10987
+ _state.canvasMap = null;
10988
+ };
10989
+
10990
+ return { init, refresh, togglePreview, dispose };
10991
+
10992
+ })();
10993
+
10345
10994
  /**
10346
10995
  * 回転できないオブジェクトの場合に設定の自動絞り込みを行う
10347
10996
  */
@@ -12593,7 +13242,7 @@ const getArrowSettings = () => {
12593
13242
  if (g_workObj.stepX_df.length === 0) {
12594
13243
  g_workObj.stepX_df = structuredClone(g_workObj.stepX);
12595
13244
  }
12596
- if (g_stateObj.swapping.endsWith(`Mirror`)) {
13245
+ if (g_settings.swappingSubs.includes(g_stateObj.swapping)) {
12597
13246
 
12598
13247
  // Swappingにおけるグループ単位での入れ替えでは、上下でステップゾーンが分かれている場合は分離してシャッフルする
12599
13248
  let _style = structuredClone(Object.values(g_workObj.shuffleGroupMap));
@@ -12613,11 +13262,11 @@ const getArrowSettings = () => {
12613
13262
  });
12614
13263
  const _styleTransDf = structuredClone(Object.values(_styleTrans));
12615
13264
 
12616
- if (g_stateObj.swapping === `Mirror`) {
13265
+ if (g_stateObj.swapping === `Mirror` || g_stateObj.swapping === `OuterSwap`) {
12617
13266
  _styleTrans.map(_group => _group.reverse());
12618
-
12619
- } else if (g_stateObj.swapping === `X-Mirror`) {
12620
- // X-Mirrorの場合、グループの内側だけ入れ替える
13267
+ }
13268
+ if (g_stateObj.swapping.endsWith(`Swap`)) {
13269
+ // グループの内側だけ入れ替える
12621
13270
  _styleTrans.forEach((group, i) => {
12622
13271
  g_settings.swapPattern.forEach(val => {
12623
13272
  swapGroupNums(_styleTrans, group, i, val);
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2026/05/09 (v47.5.3)
8
+ * Revised : 2026/05/12 (v47.6.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -1396,9 +1396,11 @@ const g_settings = {
1396
1396
  camoufrageTypes: [C_FLG_HYPHEN, `FrzArrow`],
1397
1397
  camoufrageTypeNum: 0,
1398
1398
 
1399
- swappings: [C_FLG_OFF, `Mirror`, `X-Mirror`, `Mirror+`],
1399
+ swappings: [C_FLG_OFF, `InnerSwap`, `OuterSwap`, `Mirror`, `Mirror+`],
1400
1400
  swappingNum: 0,
1401
1401
 
1402
+ swappingSubs: [`InnerSwap`, `OuterSwap`, `Mirror`],
1403
+
1402
1404
  judgRanges: [`Normal`, `Narrow`, `Hard`, `ExHard`],
1403
1405
  judgRangeNum: 0,
1404
1406
 
@@ -2292,6 +2294,7 @@ for (let j = 0; j < 260; j++) {
2292
2294
  // キーボード配列の言語設定
2293
2295
  const g_lang_kCd = {
2294
2296
  Ja: {
2297
+ 13: `Enter`,
2295
2298
  48: `0`,
2296
2299
  49: `1`,
2297
2300
  50: `2`,
@@ -2317,6 +2320,7 @@ const g_lang_kCd = {
2317
2320
  229: `IME`,
2318
2321
  },
2319
2322
  En: {
2323
+ 13: `Return`,
2320
2324
  48: `0 )`,
2321
2325
  49: `1 !`,
2322
2326
  50: `2 @`,
@@ -2433,6 +2437,7 @@ g_kCd[134] = `FN`;
2433
2437
  g_kCd[144] = `NumLk`;
2434
2438
  g_kCd[145] = `SL`;
2435
2439
  g_kCd[240] = `CapsLk`;
2440
+ g_kCd[242] = `Kana`;
2436
2441
  g_kCd[256] = `R)Shift`;
2437
2442
  g_kCd[257] = `R)Ctrl`;
2438
2443
  g_kCd[258] = `R)Alt`;
@@ -2552,6 +2557,7 @@ g_kCdN[222] = `Equal`;
2552
2557
  g_kCdN[226] = `IntlRo`;
2553
2558
  g_kCdN[229] = `Backquote`;
2554
2559
  g_kCdN[240] = `CapsLock`;
2560
+ g_kCdN[242] = `KanaMode`;
2555
2561
  g_kCdN[256] = `ShiftRight`;
2556
2562
  g_kCdN[257] = `ControlRight`;
2557
2563
  g_kCdN[258] = `AltRight`;
@@ -4554,6 +4560,8 @@ const g_lblNameObj = {
4554
4560
  'u_Random+': `Random+`,
4555
4561
  'u_S-Random': `S-Random`,
4556
4562
  'u_S-Random+': `S-Random+`,
4563
+ 'u_InnerSwap': `InnerSwap`,
4564
+ 'u_OuterSwap': `OuterSwap`,
4557
4565
  'u_Mirror+': `Mirror+`,
4558
4566
  'u_(S)': `(S)`,
4559
4567
 
@@ -4732,6 +4740,10 @@ const g_lang_lblNameObj = {
4732
4740
  'u_±120deg': `±120°`,
4733
4741
  'u_±360deg': `±360°`,
4734
4742
 
4743
+ unallocated: `未割当`,
4744
+ allocated: `割当済`,
4745
+ altAllocated: `代替キー`,
4746
+
4735
4747
  j_ii: "(・∀・)イイ!!",
4736
4748
  j_shakin: "(`・ω・)シャキン",
4737
4749
  j_matari: "( ´∀`)マターリ",
@@ -4783,6 +4795,10 @@ const g_lang_lblNameObj = {
4783
4795
  'u_±120deg': `±120deg`,
4784
4796
  'u_±360deg': `±360deg`,
4785
4797
 
4798
+ unallocated: `Unallocated`,
4799
+ allocated: `Allocated`,
4800
+ altAllocated: `Alternate Keys`,
4801
+
4786
4802
  j_ii: ":D Perfect!!",
4787
4803
  j_shakin: ":) Great!",
4788
4804
  j_matari: ":| Good",
@@ -4884,7 +4900,8 @@ const g_lang_msgObj = {
4884
4900
  effect: `矢印・フリーズアローにエフェクトをかけます。\n[Dizzy/Spin] 矢印が回転します\n[Wave/Storm] 矢印の軌道が左右に揺れます\n[Blinking] 矢印が点滅します\n[Squids] 矢印が伸び縮みします`,
4885
4901
  camoufrage: `ステップの見た目が配置は同じでランダムに変わります。`,
4886
4902
  camoufrageType: `[FrzArrow] フリーズアローの帯部分を初期表示のみ非表示にし、矢印のみで表示します(ヒット/失敗時は帯を再表示)`,
4887
- swapping: `ステップゾーンの位置を入れ替える設定です。\n[Mirror] ステップゾーンの位置をグループ単位で入れ替えます。\n[X-Mirror] ステップゾーンの中央部分のみグループ単位で入れ替えます。\n[Mirror+] ステップゾーンの位置をグループに関係なく全体的に反転します。`,
4903
+ swapping: `ステップゾーンの位置を入れ替える設定です。\n[InnerSwap] ステップゾーンの中央部分のみグループ単位で入れ替えます。\n[OuterSwap] InnerSwapの逆側(外側)をグループ単位で入れ替えます。\n` +
4904
+ `[Mirror] ステップゾーンの位置をグループ単位で入れ替えます。\n[Mirror+] ステップゾーンの位置をグループに関係なく全体的に反転します。`,
4888
4905
  judgRange: `判定の許容範囲を設定します。\n[Normal] 通常、[Narrow/Hard] 辛判定、[ExHard] 激辛判定`,
4889
4906
  autoRetry: `自動リトライの条件を設定します。\n[Miss] ミス時、[Matari] マターリ時、[Shakin] シャキン時、[FS] Fast/Slow発生時`,
4890
4907
 
@@ -4907,6 +4924,7 @@ const g_lang_msgObj = {
4907
4924
  shuffleGroup: `Mirror/X-Mirror/Turning/Random/S-Random選択時、シャッフルするグループを変更します。\n矢印の上にある同じ数字同士でシャッフルします。`,
4908
4925
  stepRtnGroup: `矢印などノーツの種類、回転に関するパターンを切り替えます。\nあらかじめ設定されている場合のみ変更可能です。`,
4909
4926
  kcReset: `対応するキーの割り当てを元に戻します。`,
4927
+ kcPreview: `キーボードレイアウトのプレビューを表示/非表示します。`,
4910
4928
 
4911
4929
  pickArrow: `色番号ごとの矢印色(枠、塗りつぶし)、通常時のフリーズアロー色(枠、帯)を\nカラーピッカーから選んで変更できます。`,
4912
4930
  pickColorR: `設定する矢印色の種類を切り替えます。`,
@@ -4986,7 +5004,8 @@ const g_lang_msgObj = {
4986
5004
  effect: `Applies effects to the arrows and freeze arrows.\n[Dizzy/Spin] Arrows rotate.\n[Wave/Storm] Swing from left to right.\n[Blinking] Arrows blink.\n[Squids] Arrows stretch and shrink.`,
4987
5005
  camoufrage: `The appearance of the steps changes randomly with the same placement.`,
4988
5006
  camoufrageType: `[FrzArrow] Initially hides freeze-arrow bars and displays only the arrow portion (bars reappear on hit/failure)`,
4989
- swapping: `This setting allows you to swap the positions of the step zones.\n[Mirror] Swaps the positions of step zones within each shuffle group. \n[X-Mirror] Swaps only the central portion of step zones within each shuffle group.\n[Mirror+] Flips the position of all step zones, regardless of shuffle groups. `,
5007
+ swapping: `This setting allows you to swap the positions of the step zones.\n[InnerSwap] Swaps only the inner portion of step zones within each shuffle group.\n[OuterSwap] Swaps the outer portion (inverse of InnerSwap) within each shuffle group.\n` +
5008
+ `[Mirror] Swaps the positions of step zones within each shuffle group. \n[Mirror+] Flips the position of all step zones, regardless of shuffle groups. `,
4990
5009
  judgRange: `Set the allowable range of judgment.\n[Normal] Normal judgment, [Narrow/Hard] Hard judgment, [ExHard] Very hard judgment`,
4991
5010
  autoRetry: `Set the conditions for automatic retry.\n[Miss] When missed, [Matari] When good, [Shakin] When great, [FS] When Fast/Slow occurs`,
4992
5011
 
@@ -5009,6 +5028,7 @@ const g_lang_msgObj = {
5009
5028
  shuffleGroup: `Change the shuffle group when Mirror, X-Mirror, Turning, Random or S-Random are selected.\nShuffle with the same numbers listed above.`,
5010
5029
  stepRtnGroup: `Switches the type of notes, such as arrows, and the pattern regarding rotation.\nThis can only be changed if it has been set in advance.`,
5011
5030
  kcReset: `Restores the corresponding key assignments.`,
5031
+ kcPreview: `Show/hide the preview of the keyboard layout.`,
5012
5032
 
5013
5033
  pickArrow: `Change the frame or fill of arrow color and the frame or bar of normal freeze-arrow color\nfor each color number from the color picker.`,
5014
5034
  pickColorR: `Switches the arrow color type to be set.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "47.5.3",
3
+ "version": "47.6.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "jsdelivr": "./js/danoni_main.js",