danoniplus 47.5.2 → 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/README.md CHANGED
@@ -8,116 +8,174 @@
8
8
  [![GitHub All Releases](https://dl-danoniplus.qu7-52m.workers.dev/badge/danoniplus/total/)](https://github.com/cwtickle/danoniplus/releases)
9
9
  [![GitHub](https://img.shields.io/github/license/cwtickle/danoniplus)](https://github.com/cwtickle/danoniplus/blob/develop/LICENSE)
10
10
  ![AboutDanOni](http://cw7.sakura.ne.jp/danoni/wiki/danonitop.png)
11
-
12
- *Dancing Onigiri "CW Edition"* is a rhythm game.
13
- You can create original game data by combining a set of published sources with music files and sequences (text file). See the [wiki](https://github.com/cwtickle/danoniplus-docs/wiki) for details.
14
11
 
15
- The source released here is the **HTML5 (HTML Living Standard) version** of the rhythm game *"Dancing Onigiri"* that was previously released as Flash.
12
+ [ **English** | [**日本語**](#Japanese) ]
16
13
 
17
- Dancing☆Onigiri (CW Edition)は、ブラウザで動作するキーボードを使ったリズムゲームです。
18
- 公開しているソース一式と、音楽ファイル・譜面データ(テキスト)を組み合わせることで
19
- オリジナルのゲームデータを作ることができます。詳細は[Wiki](https://github.com/cwtickle/danoniplus/wiki)をご覧ください。
20
-
21
- ここで公開しているソースは、以前Flashとして公開していたリズムゲーム
22
- 「Dancing☆Onigiri」の **HTML5 (HTML Living Standard)** 版です。
23
- これまでのParaFla版に比べ、さまざまな機能強化を行っています。
14
+ *Dancing☆Onigiri (CW Edition)* is a high-performance, keyboard-based rhythm game engine that runs natively in web browsers.
15
+ 👉 [**How to Play**](https://github.com/cwtickle/danoniplus-docs/wiki/) /
16
+ 👉 [**Game system overview**](https://github.com/cwtickle/danoniplus-docs/wiki/AboutGameSystem) /
17
+ 👉 [**Shortcut**](https://github.com/cwtickle/danoniplus-docs/wiki/Shortcut)
24
18
 
25
19
  [![Download](https://dl-danoniplus.qu7-52m.workers.dev/dl-button/danoniplus/)](https://dl-danoniplus.qu7-52m.workers.dev/danoniplus/latest/archive.zip)
26
20
 
27
- ## Demo
21
+ - 📹 [Introducing Dancing☆Onigiri (Twitch)](https://www.twitch.tv/videos/1601561414?collection=svxqeEFHGBfVdg)
22
+ - 📹 [Introducing Dancing☆Onigiri (YouTube)](https://www.youtube.com/watch?v=ESTdqf8hpos)
28
23
 
29
- - [Demo1](https://cw7.sakura.ne.jp/danoni/2013/0237_Cllema.html) クレマ / Kinoshita Tamaki
30
- - [Demo2](https://cw7.sakura.ne.jp/danoni/2017/0305_ShiningStar.html) シャイニングスター / MaouDamashii
31
- - [Demo3](https://cw7.sakura.ne.jp/danoni/2018/0315_PetitMagie.html) プチ・マギエ / Napi
24
+ ## 🚀 Features
32
25
 
33
- ## How to Play / 遊び方
26
+ - **Multi-Platform:** Works on any modern web browser. Using keyboard.
27
+ - **Flexible Key Modes:** Supports 5-key, 7-key, 9-key, and more.
28
+ In addition to **20+ standard modes**, you can define your own **Custom Key Modes**!
29
+ - **Easy Deployment:** No complex backend required. Just upload to your web server.
30
+ - **Highly Customizable:** Highly extensible via advanced chart settings and custom JavaScript (safe execution support).
34
31
 
35
- This is a rhythm game, using Keyboard on the website.
36
- There are many playstyles in the *Dancing Onigiri*. For example, 5keys, 7keys, 11keys, etc.
37
- The details are as follows.
38
- -> [How to Play](https://github.com/cwtickle/danoniplus-docs/wiki/AboutGameSystem)
32
+ <img src="https://user-images.githubusercontent.com/44026291/193052877-fc0b5dc6-307c-4311-befa-22ba29580413.png" width="45%"><img src="https://user-images.githubusercontent.com/44026291/226273587-7804ca76-c1a5-490b-9f29-d8f370217d08.png" width="45%">
39
33
 
40
- リズムに合わせてやってくる矢印・フリーズアローを、ステップゾーン上で押すリズムゲームです。
41
- キーボードを使って遊びます。
42
- 下記は7keyの例ですが、他にも5keyや11keyなど多様なプレイスタイルがあります。
34
+ ## 📦 Quick Start
43
35
 
44
- 詳細は下記をご覧ください。
45
- -> [How to Play](https://github.com/cwtickle/danoniplus/wiki/AboutGameSystem)
36
+ 1. Download the latest version from [Releases](https://github.com/cwtickle/danoniplus/releases).
37
+ 2. Open `danoni/danoni1.html` in your browser.
38
+ 3. Enjoy the pre-installed songs immediately!
46
39
 
47
- ![HowtoPlay](http://cw7.sakura.ne.jp/danoni/wiki/howtoplay1.png)
48
- (The image is under development. / 画像は開発中のものです)
40
+ ## 📖 Documentation (Wiki)
49
41
 
50
- ### Rule / ゲームのルール
42
+ For more details, please refer to the Wiki:
51
43
 
52
- When you press the keyboard with good timing *:D Perfect!!* *:) O.K.* and life will go up.
53
- On the other hand, if you remove the timing *:( Bad*, *:_( Miss*, *:( N.G.* will be, life falls.
54
- If the life of the life gauge remains by the end of the game or it is over the quota, the game is cleared.
44
+ - [🚀 Creating chart Using the Preview Site](https://github.com/cwtickle/danoniplus-docs/wiki/HowToUsePreview-Example)
45
+ - [📝 How to make chart overview](https://github.com/cwtickle/danoniplus-docs/wiki/HowtoMake)
55
46
 
56
- タイミング良くキーボードを押すと(・∀・)イイ!!や(゚∀゚)キター!!となり、ライフが上がります。
57
- 一方、タイミングを外すと(´・ω・\`)ショボーン、(\`Д´)ウワァン、(・A・)イクナイとなり、ライフが下がります。
58
- ゲーム終了までにライフゲージのライフが残っているか、ノルマ以上であればゲームクリアです。
47
+ Need help? Join our community on [Discord (Dancing☆Onigiri Server)](https://discord.gg/YVWUdUGyMy)!
59
48
 
60
- ## Works / 公開作品
49
+ ## 🎮 Playable Demos
50
+ Experience *Dancing☆Onigiri* in action:
61
51
 
62
- - [Dancing☆Onigiri 難易度表 for.js](http://dodl4.g3.xrea.com/)
63
- - [多鍵データベース](http://apoi108.sakura.ne.jp/danoni/ta/)
64
- - [メジャー多鍵データベース](https://apoi108.sakura.ne.jp/danoni/danoni_all_list/)
65
- - [Dancing☆Onigiri サイト一覧](https://cw7.sakura.ne.jp/danonidb/)
66
- - [Dancing☆Onigiri 作品一覧](https://cw7.sakura.ne.jp/lst/)
52
+ - [Demo1 (7key, 11key)](https://cw7.sakura.ne.jp/danoni/2013/0237_Cllema.html) Cllema / Kinoshita Tamaki
53
+ - [Demo2 (9Akey)](https://cw7.sakura.ne.jp/danoni/2017/0305_ShiningStar.html) Shining Star / MaouDamashii
54
+ - [Demo3 (11Wkey)](https://cw7.sakura.ne.jp/danoni/2018/0315_PetitMagie.html) Petit Magie / Napi
55
+
56
+ ## 🌐 Ecosystem & Works
57
+ There are many titles to choose from. Start with a level that suits you!
58
+
59
+ - [Dancing☆Onigiri Difficulty Table for.js](http://dodl4.g3.xrea.com/)
60
+ - [The danoni database for minor keymodes](http://apoi108.sakura.ne.jp/danoni/ta/)
61
+ - [The danoni database for simpler keymodes](https://apoi108.sakura.ne.jp/danoni/danoni_all_list/)
62
+ - [Dancing☆Onigiri Site List](https://cw7.sakura.ne.jp/danonidb/)
67
63
 
68
- ## How to Make / 作り方
64
+ ## 🤝 How to Contribute
65
+ We welcome contributions of all kinds—from bug reports to new features!
66
+
67
+ - [Contribution Guide](https://github.com/cwtickle/danoniplus/blob/develop/.github/CONTRIBUTING.md)
68
+ - [Contributors](https://github.com/cwtickle/danoniplus/blob/develop/CONTRIBUTORS.md)
69
+ - Discussion: Join the `#ソース` channel on [Discord](https://discord.gg/YVWUdUGyMy).
70
+
71
+ ## 🏠 Portal Site & Community
72
+ Join the Dancing Onigiri Discord server! We hold regular events there.
73
+
74
+ - [Dancing☆Onigiri Wiki](https://wikiwiki.jp/danoniplus/)
75
+ - [Dancing☆Onigiri Discord server](https://discord.gg/TegbHFY7zg)
76
+ - [X #danoni](https://x.com/search?q=%23danoni&src=typed_query&f=live)
69
77
 
70
- - [How to make chart overview](https://github.com/cwtickle/danoniplus-docs/wiki/HowtoMake) / [譜面の作成概要](https://github.com/cwtickle/danoniplus/wiki/HowtoMake)
71
- - [Creating chart Using the Preview Site](https://github.com/cwtickle/danoniplus-docs/wiki/HowToUsePreview-Example) / [プレビューサイトを使った譜面の作成](https://github.com/cwtickle/danoniplus/wiki/HowToUsePreview-Example)
72
- - [ParaFla!ソース利用者向け移行方法 (Japanese Only)](https://github.com/cwtickle/danoniplus/wiki/forParaFlaUser)
78
+ ## 🛠️ Related Tools Repository
73
79
 
74
- ### How to Install / 導入方法
80
+ ### Dancing☆Onigiri
75
81
 
76
- - Download from [Changelog](https://github.com/cwtickle/danoniplus-docs/wiki/Changelog-latest). You can also install from npm.
77
- - [Releases](https://github.com/cwtickle/danoniplus/releases)からダウンロードします。npmからの導入も可能です。
78
- ```
79
- npm i danoniplus
80
- ```
81
- - When directly specifying the version name, specify the version after @ as shown below.
82
- - バージョン名を直接指定するときは下記のように @以降をバージョン指定します。
83
- ```
84
- npm i danoniplus@26.1.2
85
- ```
82
+ - [Dancing☆Onigiri Preview](https://danonicw.skr.jp/) ([Repository](https://github.com/cwtickle/danoniplus-preview)) @cwtickle
83
+ - [Dancing☆Onigiri Editor(for CW Edition)](https://github.com/superkuppabros/danoni-editor) @superkuppabros
84
+ - [Tool for Converting Dan-Oni Song Data to JS file](https://github.com/suzme/danoni-base64) @suzme
85
+ - [Dan-Oni Arrow Color Tool](https://github.com/suzme/danoni-colorpicker) @suzme
86
+ - [Gauge Calculator for Dan-Oni](http://www.omission0.com/other/gauge_calculator/) @goe0
87
+ - [Waveform Analysis Tool for Dan-Oni](https://github.com/suzme/danoni-waveform) @suzme
88
+ - [Dan-Oni StepViewer](https://github.com/ittn-exe/danoni_stepViewer) @ittn-exe
86
89
 
87
- ## System requirements / 動作環境
90
+ #### CustomJS
88
91
 
89
- - Windows, Mac OS, Linux, Android, iPad OS
90
- - Google Chrome, Microsoft Edge, Opera, Vivaldi, Safari (WebKit), Firefox (Gecko)
92
+ - [Dancing☆Onigiri 9tkey](https://github.com/suzme/danoni-9t) @suzme
93
+ - [danoni-key-change](https://github.com/suzme/danoni-key-change) @suzme
91
94
 
92
- ## Difference from Flash Version / Flash版との差異 (Japanese Only)
95
+ ### Kirizma / Keyboard Rhythmer
93
96
 
94
- *Dancing Onigiri "CW Edition"* basically conforms to the specifications of *ParaFla!* Version, but the details are different from the conventional ones. See below for details.
97
+ - [Kirizma (CW Edition)](https://github.com/cwtickle/kirizma-cw) @cwtickle
98
+ - [Chart character converter from Dan-Oni](https://github.com/suzme/kirizma-converter) @suzme
99
+ - [Kirizma Lyrics Display Generator](https://github.com/prlg25/kirizma_lyric) @prlg25
100
+ - [The difficulty table for Kirizma](https://github.com/suzme/kirizma) @suzme
95
101
 
96
- Dancing☆Onigiri (CW Edition)では基本的にParaFla!版の仕様に準拠していますが、
97
- 細かい点が従来と異なります。詳細は下記をご覧ください。
102
+ ### Punching◇Panels
98
103
 
99
- - [Difference from Flash Version / Flash版との差異 (Japanese)](https://github.com/cwtickle/danoniplus/wiki/DifferenceFromFlashVer)
104
+ - [Punching◇Panels](https://github.com/cwtickle/punching-panels) @cwtickle
105
+ - [Punching◇Panels Editor](https://github.com/suzme/punpane-editor) @suzme
106
+ - [The difficulty table for Punching◇Panels](https://github.com/suzme/punpane) @suzme
100
107
 
101
- ## How to Contribute / 開発者の方へ
108
+ ## ⚖️ License
102
109
 
103
- If you would like to cooperate with the development, please see below. Even if you don't have a GitHub account, you can cooperate!
110
+ This software is released under the MIT License, see LICENSE.
104
111
 
105
- 開発にご協力いただける方は、下記をご覧ください。GitHubアカウントの無い方でも協力できます!
112
+ ---
106
113
 
107
- - [How to Contribute / 貢献の仕方](https://github.com/cwtickle/danoniplus/blob/develop/.github/CONTRIBUTING.md)
108
- - [GitLab community for requests and bug reports / 要望・不具合報告(GitLab Issues)](https://gitlab.com/cwtickle/danonicw/-/issues)
109
- - [Contributors / コントリビューター](https://github.com/cwtickle/danoniplus/blob/develop/CONTRIBUTORS.md)
114
+ <a id="Japanese"></a>
110
115
 
111
- ## Portal Site / ポータルサイト
116
+ [ **[English](#top)** | **日本語** ]
112
117
 
113
- - [Dancing☆Onigiri Wiki](https://wikiwiki.jp/danoniplus/)
118
+ *Dancing☆Onigiri (CW Edition)* は、Webブラウザ上で動作するキーボード操作のリズムゲームエンジンです。
119
+ 👉 [**遊び方**](https://github.com/cwtickle/danoniplus/wiki/) /
120
+ 👉 [**操作説明**](https://github.com/cwtickle/danoniplus/wiki/AboutGameSystem) /
121
+ 👉 [**ショートカット一覧**](https://github.com/cwtickle/danoniplus/wiki/Shortcut)
114
122
 
115
- ## Community / コミュニティ
123
+ [![Download](https://dl-danoniplus.qu7-52m.workers.dev/dl-button/danoniplus/)](https://dl-danoniplus.qu7-52m.workers.dev/danoniplus/latest/archive.zip)
116
124
 
125
+ - 📹 [Dancing☆Onigiriの紹介 (Twitch)](https://www.twitch.tv/videos/1601561414?collection=svxqeEFHGBfVdg)
126
+ - 📹 [Dancing☆Onigiriの紹介 (YouTube)](https://www.youtube.com/watch?v=ESTdqf8hpos)
127
+
128
+ ## 🚀 特徴
129
+ - **マルチプラットフォーム:** モダンなブラウザがあれば、PCでもスマホでも動作します。
130
+ - **多彩なキーモード:** 5key、7key、9Akeyなど、20種類以上の標準モードに加え、独自の**カスタムキーモード**も作成可能です。
131
+ - **容易な起動:** サーバーにファイルをアップロードするだけで、すぐに自分の譜面を公開できます。
132
+ - **高度なカスタマイズ性:** 豊富な譜面設定やカスタムJavaScript(安全な実行環境をサポート)により、自由度の高い演出が可能です。
133
+
134
+ <img src="https://user-images.githubusercontent.com/44026291/193052561-235aa8b9-43a3-4045-a417-26fbced5beb0.png" width="45%"><img src="https://user-images.githubusercontent.com/44026291/226273658-7afdc349-4e0c-4ae8-9d70-2f30eb80ab5f.png" width="45%">
135
+
136
+ ## 📦 クイックスタート
137
+ 1. [リリースページ](https://github.com/cwtickle/danoniplus/releases) から最新版をダウンロードします。
138
+ 2. `danoni/danoni1.html` をブラウザで開きます。
139
+ 3. プリセット曲ですぐに動作を確認できます。
140
+
141
+ ## 📖 ドキュメント (Wiki)
142
+ 詳細な使い方は公式Wikiを確認してください:
143
+
144
+ - [🚀 プレビューサイトを使った譜面作成](https://github.com/cwtickle/danoniplus/wiki/HowToUsePreview-Example)
145
+ - [📝 譜面制作の概要](https://github.com/cwtickle/danoniplus/wiki/HowtoMake)
146
+
147
+ 質問や交流は [Dancing☆Onigiri Discord サーバー](https://discord.gg/YVWUdUGyMy) の「#一般」チャンネルへどうぞ!
148
+
149
+ ## 🎮 デモ
150
+ Dancing☆Onigiriを実際にプレイしてみましょう!
151
+
152
+ - [Demo1 (7key, 11key)](https://cw7.sakura.ne.jp/danoni/2013/0237_Cllema.html) クレマ / 木下たまき
153
+ - [Demo2 (9Akey)](https://cw7.sakura.ne.jp/danoni/2017/0305_ShiningStar.html) シャイニングスター / 魔王魂
154
+ - [Demo3 (11Wkey)](https://cw7.sakura.ne.jp/danoni/2018/0315_PetitMagie.html) プチ・マギエ / Napi
155
+
156
+ ## 🌐 公開作品
157
+ 数多くの作品が公開されています。自分に合った難易度から始めていきましょう!
158
+
159
+ - [Dancing☆Onigiri 難易度表 for.js](http://dodl4.g3.xrea.com/)
160
+ - [多鍵データベース](http://apoi108.sakura.ne.jp/danoni/ta/)
161
+ - [メジャー多鍵データベース](https://apoi108.sakura.ne.jp/danoni/danoni_all_list/)
162
+ - [Dancing☆Onigiri サイト一覧](https://cw7.sakura.ne.jp/danonidb/)
163
+
164
+ ## 🤝 リポジトリへの貢献方法
165
+ Dancing☆Onigiri (CW Edition)に関する機能要望・不具合報告については下記をご覧ください。参加歓迎です。
166
+
167
+ - [Contribution Guide](https://github.com/cwtickle/danoniplus/blob/develop/.github/CONTRIBUTING.md)
168
+ - [Contributors](https://github.com/cwtickle/danoniplus/blob/develop/CONTRIBUTORS.md)
169
+ - [Dancing☆Onigiri Discord サーバー](https://discord.gg/YVWUdUGyMy)の`#ソース`チャンネルで各種要望・不具合報告を受け付けています。
170
+
171
+ ## 🏠 関連ポータル・コミュニティ
172
+ Dancing☆OnigiriのDiscordサーバーにぜひお越しください! 定期的にイベントも開催しています。
173
+
174
+ - [Dancing☆Onigiri Wiki](https://wikiwiki.jp/danoniplus/)
117
175
  - [Dancing☆Onigiri Discord server](https://discord.gg/TegbHFY7zg)
118
- - [X #danoni](https://x.com/search?q=%23danoni&src=typed_query&f=live)
176
+ - [X (旧Twitter) #danoni](https://x.com/search?q=%23danoni&src=typed_query&f=live)
119
177
 
120
- ## Related Tools Repository / 関連リポジトリ・ツール
178
+ ## 🛠️ 関連ツール・リポジトリ
121
179
 
122
180
  ### Dancing☆Onigiri
123
181
 
@@ -127,14 +185,14 @@ If you would like to cooperate with the development, please see below. Even if y
127
185
  - [ダンおに矢印色ツール](https://github.com/suzme/danoni-colorpicker) @suzme
128
186
  - [ダンおにゲージ計算機 (Gauge Calculator)](http://www.omission0.com/other/gauge_calculator/) @goe0
129
187
  - [ダンおに波形解析ツール](https://github.com/suzme/danoni-waveform) @suzme
130
- - [danoni_stepViewer](https://github.com/ittn-exe/danoni_stepViewer) @ittn-exe
188
+ - [ダンおに譜面ビューア](https://github.com/ittn-exe/danoni_stepViewer) @ittn-exe
131
189
 
132
- #### CustomJS / カスタマイズ
190
+ #### カスタムスクリプト
133
191
 
134
192
  - [Dancing☆Onigiri 9tkey](https://github.com/suzme/danoni-9t) @suzme
135
193
  - [danoni-key-change](https://github.com/suzme/danoni-key-change) @suzme
136
194
 
137
- #### Conversion Tool from Legacy Flash Environments / 旧Flashからの変換ツール
195
+ #### 旧Flashからの変換ツール
138
196
 
139
197
  - [Dancing☆Onigiri Chart Reverser](https://github.com/cwtickle/danoniplus-reverser) @cwtickle
140
198
  - [Dancing☆Onigiri Chart Converter](https://github.com/cwtickle/danoniplus-converter) @cwtickle
@@ -152,6 +210,5 @@ If you would like to cooperate with the development, please see below. Even if y
152
210
  - [Punching◇Panels エディター](https://github.com/suzme/punpane-editor) @suzme
153
211
  - [Punching◇Panels 難易度表](https://github.com/suzme/punpane) @suzme
154
212
 
155
- ## License / ライセンス
156
-
157
- This software is released under the MIT License, see LICENSE.
213
+ ## ⚖️ ライセンス (License)
214
+ このソフトウェアは **MIT License** のもとで公開されています。詳細は LICENSE ファイルをご確認ください。
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/04
7
+ * Revised : 2026/05/12
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 47.5.2`;
12
- const g_revisedDate = `2026/05/04`;
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);
@@ -13220,6 +13869,10 @@ const mainInit = () => {
13220
13869
  jdgY[0] += g_diffObj.arrowJdgY;
13221
13870
  jdgY[1] += g_diffObj.frzJdgY;
13222
13871
  }
13872
+ if (g_stateObj.playWindow === `SideScroll`) {
13873
+ jdgX[0] += 30;
13874
+ jdgX[1] -= 60;
13875
+ }
13223
13876
 
13224
13877
  jdgGroups.forEach((jdg, j) => {
13225
13878
  // キャラクタ表示
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2026/05/04 (v47.5.2)
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`;
@@ -2728,8 +2734,6 @@ const g_shortcutObj = {
2728
2734
  ControlLeft_KeyC: { id: `` },
2729
2735
  ControlRight_KeyC: { id: `` },
2730
2736
  KeyC: { id: `lnkHighScore`, reset: true },
2731
- Comma: { id: `btnSpdCursorL` },
2732
- Period: { id: `btnSpdCursorR` },
2733
2737
 
2734
2738
  Escape: { id: `btnBack` },
2735
2739
  Space: { id: `btnKeyConfig` },
@@ -2812,6 +2816,8 @@ const g_shortcutObj = {
2812
2816
  ControlLeft_KeyC: { id: `` },
2813
2817
  ControlRight_KeyC: { id: `` },
2814
2818
  KeyC: { id: `lnkHighScore`, reset: true },
2819
+ Comma: { id: `btnSpdCursorL` },
2820
+ Period: { id: `btnSpdCursorR` },
2815
2821
 
2816
2822
  Escape: { id: `btnBack` },
2817
2823
  Space: { id: `btnKeyConfig` },
@@ -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.2",
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",