Compare commits
9 Commits
44f13b1aeb
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1000736389 | |||
| e999b2ff5c | |||
| c658bb6cd7 | |||
| a69d47b879 | |||
| e749618afd | |||
| 59f2880498 | |||
| 7531eacf64 | |||
| 8825bc38d7 | |||
| aa1ab8f533 |
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
IndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
ColumnLimit: 120
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
IndentCaseLabels: true
|
||||||
|
SortIncludes: Never
|
||||||
|
ReflowComments: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReferenceAlignment: Left
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
...
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
<html><head>
|
||||||
|
<title>301 Moved Permanently</title>
|
||||||
|
</head><body>
|
||||||
|
<h1>Moved Permanently</h1>
|
||||||
|
<p>The document has moved <a href="https://www.wtfpl.net/txt/copying/">here</a>.</p>
|
||||||
|
<hr>
|
||||||
|
<address>Apache/2.4.66 (Debian) Server at www.wtfpl.net Port 80</address>
|
||||||
|
</body></html>
|
||||||
@@ -9,6 +9,7 @@ Reusable NVGT helpers for Storm projects.
|
|||||||
- `text_reader_compat.nvgt`: Optional aliases (`text_reader*`) for legacy code.
|
- `text_reader_compat.nvgt`: Optional aliases (`text_reader*`) for legacy code.
|
||||||
- `ui.nvgt`: Dialog wrappers with optional main-window restoration.
|
- `ui.nvgt`: Dialog wrappers with optional main-window restoration.
|
||||||
- `speech_history.nvgt`: `speak_with_history()` wrapper plus comma/period history navigation.
|
- `speech_history.nvgt`: `speak_with_history()` wrapper plus comma/period history navigation.
|
||||||
|
- `character_dialog.nvgt`: Blocking dialog lines with repeat/next/skip keys and optional per-word pre-speech sounds.
|
||||||
- `notifications.nvgt`: Queued notifications with history and optional sound playback.
|
- `notifications.nvgt`: Queued notifications with history and optional sound playback.
|
||||||
- `menu_helpers.nvgt`: Simple menu runner + filter helpers with `sounds/menu` defaults.
|
- `menu_helpers.nvgt`: Simple menu runner + filter helpers with `sounds/menu` defaults.
|
||||||
- `menu_music.nvgt`: Reusable menu music start/pause/stop helpers with blocking fade-out pause by default.
|
- `menu_music.nvgt`: Reusable menu music start/pause/stop helpers with blocking fade-out pause by default.
|
||||||
@@ -29,6 +30,7 @@ Reusable NVGT helpers for Storm projects.
|
|||||||
#include "libstorm-nvgt/text_reader_compat.nvgt" // Optional legacy aliases
|
#include "libstorm-nvgt/text_reader_compat.nvgt" // Optional legacy aliases
|
||||||
#include "libstorm-nvgt/ui.nvgt"
|
#include "libstorm-nvgt/ui.nvgt"
|
||||||
#include "libstorm-nvgt/speech_history.nvgt"
|
#include "libstorm-nvgt/speech_history.nvgt"
|
||||||
|
#include "libstorm-nvgt/character_dialog.nvgt"
|
||||||
#include "libstorm-nvgt/notifications.nvgt"
|
#include "libstorm-nvgt/notifications.nvgt"
|
||||||
#include "libstorm-nvgt/menu_helpers.nvgt"
|
#include "libstorm-nvgt/menu_helpers.nvgt"
|
||||||
#include "libstorm-nvgt/menu_music.nvgt"
|
#include "libstorm-nvgt/menu_music.nvgt"
|
||||||
@@ -110,6 +112,22 @@ void stop_main_menu_music() {
|
|||||||
file_viewer_file("README.txt", "Readme", true);
|
file_viewer_file("README.txt", "Readme", true);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## character_dialog Example
|
||||||
|
|
||||||
|
```nvgt
|
||||||
|
#include "libstorm-nvgt/character_dialog.nvgt"
|
||||||
|
|
||||||
|
void play_intro_dialog() {
|
||||||
|
string[] lines = {"Welcome to the gate.", "State your name and purpose."};
|
||||||
|
character_dialog_show_lines(lines);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```nvgt
|
||||||
|
// Optional override sound. Custom paths play once per line instead of once per word.
|
||||||
|
character_dialog_set_sound_path("sounds/cutscene/chime");
|
||||||
|
```
|
||||||
|
|
||||||
## speech_history + notifications Example
|
## speech_history + notifications Example
|
||||||
|
|
||||||
```nvgt
|
```nvgt
|
||||||
@@ -140,7 +158,8 @@ void on_event() {
|
|||||||
## learn_sounds Example (Project Override File)
|
## learn_sounds Example (Project Override File)
|
||||||
|
|
||||||
```nvgt
|
```nvgt
|
||||||
// In your project root, create excluded_sounds.nvgt:
|
// The override file can be named and placed anywhere in your project.
|
||||||
|
// Example file: src/sound_settings.nvgt
|
||||||
void configure_project_learn_sounds() {
|
void configure_project_learn_sounds() {
|
||||||
learn_sounds_reset_configuration();
|
learn_sounds_reset_configuration();
|
||||||
learn_sounds_add_skip_entry("sounds/menu/");
|
learn_sounds_add_skip_entry("sounds/menu/");
|
||||||
@@ -151,7 +170,7 @@ void configure_project_learn_sounds() {
|
|||||||
```nvgt
|
```nvgt
|
||||||
// In your game module:
|
// In your game module:
|
||||||
#include "libstorm-nvgt/learn_sounds.nvgt"
|
#include "libstorm-nvgt/learn_sounds.nvgt"
|
||||||
#include "excluded_sounds.nvgt"
|
#include "src/sound_settings.nvgt"
|
||||||
#include "libstorm-nvgt/speech_history.nvgt"
|
#include "libstorm-nvgt/speech_history.nvgt"
|
||||||
|
|
||||||
void learn_sounds_bridge_speak(const string &in message, bool interrupt) {
|
void learn_sounds_bridge_speak(const string &in message, bool interrupt) {
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
funcdef void character_dialog_speak_callback(const string& in message, bool interrupt);
|
||||||
|
|
||||||
|
string characterDialogSoundPath = "sounds/dialog";
|
||||||
|
bool characterDialogShowUsageInstructions = true;
|
||||||
|
bool characterDialogUsageInstructionsShown = false;
|
||||||
|
bool characterDialogSoundLoaded = false;
|
||||||
|
sound characterDialogSound;
|
||||||
|
character_dialog_speak_callback @characterDialogSpeakCallback = null;
|
||||||
|
|
||||||
|
void character_dialog_set_speak_callback(character_dialog_speak_callback @callback) {
|
||||||
|
@characterDialogSpeakCallback = @callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_set_sound_path(const string soundPath) {
|
||||||
|
characterDialogSoundPath = soundPath;
|
||||||
|
characterDialogSoundLoaded = false;
|
||||||
|
characterDialogSound.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_set_show_usage_instructions(bool enabled) {
|
||||||
|
characterDialogShowUsageInstructions = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_reset_usage_instructions() {
|
||||||
|
characterDialogUsageInstructionsShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_reset() {
|
||||||
|
characterDialogUsageInstructionsShown = false;
|
||||||
|
characterDialogSoundLoaded = false;
|
||||||
|
characterDialogSound.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_speak(const string& in message, bool interrupt) {
|
||||||
|
if (@characterDialogSpeakCallback !is null) {
|
||||||
|
characterDialogSpeakCallback(message, interrupt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_reader_speak(message, interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_should_skip() {
|
||||||
|
return key_pressed(KEY_ESCAPE) || key_pressed(KEY_AC_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_ensure_sound_loaded() {
|
||||||
|
if (characterDialogSoundLoaded) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
characterDialogSound.close();
|
||||||
|
if (characterDialogSound.load(characterDialogSoundPath)) {
|
||||||
|
characterDialogSoundLoaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characterDialogSound.load(characterDialogSoundPath + ".ogg")) {
|
||||||
|
characterDialogSoundLoaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!characterDialogSound.load(characterDialogSoundPath + ".wav")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
characterDialogSoundLoaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_use_word_sound_sequence() {
|
||||||
|
if (characterDialogSoundPath == "sounds/dialog" || characterDialogSoundPath == "sounds/dialog.ogg" ||
|
||||||
|
characterDialogSoundPath == "sounds/dialog.wav") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int character_dialog_count_words(const string& in message) {
|
||||||
|
if (message == "") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wordCount = 0;
|
||||||
|
bool inWord = false;
|
||||||
|
for (uint charIndex = 0; charIndex < message.length(); charIndex++) {
|
||||||
|
string character = message.substr(charIndex, 1);
|
||||||
|
bool isWhitespace = character == " " || character == "\t" || character == "\n" || character == "\r";
|
||||||
|
if (isWhitespace) {
|
||||||
|
inWord = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inWord) {
|
||||||
|
wordCount++;
|
||||||
|
inWord = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wordCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_play_word_sound_sequence(const string& in message) {
|
||||||
|
if (!character_dialog_ensure_sound_loaded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int playCount = 1;
|
||||||
|
if (character_dialog_use_word_sound_sequence()) {
|
||||||
|
playCount = character_dialog_count_words(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int wordIndex = 0; wordIndex < playCount; wordIndex++) {
|
||||||
|
characterDialogSound.play();
|
||||||
|
while (characterDialogSound.playing) {
|
||||||
|
wait(5);
|
||||||
|
if (character_dialog_should_skip()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_repeat_requested() {
|
||||||
|
key_code[] @pressedKeys = keys_pressed();
|
||||||
|
if (@pressedKeys is null || pressedKeys.length() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (uint keyIndex = 0; keyIndex < pressedKeys.length(); keyIndex++) {
|
||||||
|
int keyCode = pressedKeys[keyIndex];
|
||||||
|
if (keyCode == KEY_RETURN || keyCode == KEY_NUMPAD_ENTER || keyCode == KEY_ESCAPE || keyCode == KEY_AC_BACK)
|
||||||
|
continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int character_dialog_wait_for_action(const string& in currentLine, bool interrupt) {
|
||||||
|
while (true) {
|
||||||
|
wait(5);
|
||||||
|
|
||||||
|
if (character_dialog_should_skip()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key_pressed(KEY_RETURN) || key_pressed(KEY_NUMPAD_ENTER)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool repeatRequested = character_dialog_repeat_requested();
|
||||||
|
if (repeatRequested) {
|
||||||
|
character_dialog_speak(currentLine, interrupt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool character_dialog_show_lines(const string[] @dialogLines, bool interrupt = true) {
|
||||||
|
if (@dialogLines is null || dialogLines.length() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint lineIndex = 0; lineIndex < dialogLines.length(); lineIndex++) {
|
||||||
|
string currentLine = dialogLines[lineIndex];
|
||||||
|
if (currentLine == "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character_dialog_play_word_sound_sequence(currentLine)) {
|
||||||
|
character_dialog_speak(" ", true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string displayLine = currentLine;
|
||||||
|
if (characterDialogShowUsageInstructions && !characterDialogUsageInstructionsShown) {
|
||||||
|
displayLine += " Press enter to continue, escape to skip, or any other key to repeat.";
|
||||||
|
characterDialogUsageInstructionsShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
character_dialog_speak(displayLine, interrupt);
|
||||||
|
int action = character_dialog_wait_for_action(currentLine, interrupt);
|
||||||
|
if (action < 0) {
|
||||||
|
character_dialog_speak(" ", true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
character_dialog_speak(" ", true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void character_dialog_show(const string[] @dialogLines, bool interrupt = true) {
|
||||||
|
/* The orange goblins speak to me in the night,
|
||||||
|
as the moon casts shadows pumpkins come to life! */
|
||||||
|
character_dialog_show_lines(dialogLines, interrupt);
|
||||||
|
}
|
||||||
+15
-9
@@ -6,7 +6,7 @@ string log_format_timestamp() {
|
|||||||
return "[" + stamp + "]";
|
return "[" + stamp + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
string log_flatten_multiline(const string&in text) {
|
string log_flatten_multiline(const string& in text) {
|
||||||
string result = "";
|
string result = "";
|
||||||
bool lastWasSeparator = false;
|
bool lastWasSeparator = false;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ string log_flatten_multiline(const string&in text) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool log_append_line(const string&in logPath, const string&in message) {
|
bool log_append_line(const string& in logPath, const string& in message) {
|
||||||
file logFile;
|
file logFile;
|
||||||
if (!logFile.open(logPath, "ab")) {
|
if (!logFile.open(logPath, "ab")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -39,7 +39,7 @@ bool log_append_line(const string&in logPath, const string&in message) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_unhandled_exception_to_file(const string&in logPath = "crash.log", const string&in context = "") {
|
void log_unhandled_exception_to_file(const string& in logPath = "crash.log", const string& in context = "") {
|
||||||
string info = get_exception_info();
|
string info = get_exception_info();
|
||||||
string filePath = get_exception_file();
|
string filePath = get_exception_file();
|
||||||
int line = get_exception_line();
|
int line = get_exception_line();
|
||||||
@@ -47,12 +47,18 @@ void log_unhandled_exception_to_file(const string&in logPath = "crash.log", cons
|
|||||||
string stack = log_flatten_multiline(last_exception_call_stack);
|
string stack = log_flatten_multiline(last_exception_call_stack);
|
||||||
|
|
||||||
string message = "Unhandled exception";
|
string message = "Unhandled exception";
|
||||||
if (context != "") message += " (" + context + ")";
|
if (context != "")
|
||||||
if (info != "") message += ": " + info;
|
message += " (" + context + ")";
|
||||||
if (filePath != "") message += " at " + filePath;
|
if (info != "")
|
||||||
if (line > 0) message += ":" + line;
|
message += ": " + info;
|
||||||
if (func != "") message += " in " + func;
|
if (filePath != "")
|
||||||
if (stack != "") message += " | stack: " + stack;
|
message += " at " + filePath;
|
||||||
|
if (line > 0)
|
||||||
|
message += ":" + line;
|
||||||
|
if (func != "")
|
||||||
|
message += " in " + func;
|
||||||
|
if (stack != "")
|
||||||
|
message += " | stack: " + stack;
|
||||||
|
|
||||||
log_append_line(logPath, message);
|
log_append_line(logPath, message);
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-20
@@ -1,12 +1,15 @@
|
|||||||
// Dictionary utility helpers.
|
// Dictionary utility helpers.
|
||||||
|
|
||||||
double dict_get_number(dictionary@ data, const string&in key, double defaultValue) {
|
double dict_get_number(dictionary @data, const string& in key, double defaultValue) {
|
||||||
double value = 0.0;
|
double value = 0.0;
|
||||||
if (@data is null) return defaultValue;
|
if (@data is null)
|
||||||
if (data.get(key, value)) return value;
|
return defaultValue;
|
||||||
|
if (data.get(key, value))
|
||||||
|
return value;
|
||||||
|
|
||||||
int valueInt = 0;
|
int valueInt = 0;
|
||||||
if (data.get(key, valueInt)) return valueInt;
|
if (data.get(key, valueInt))
|
||||||
|
return valueInt;
|
||||||
|
|
||||||
string valueString = "";
|
string valueString = "";
|
||||||
if (data.get(key, valueString)) {
|
if (data.get(key, valueString)) {
|
||||||
@@ -16,43 +19,54 @@ double dict_get_number(dictionary@ data, const string&in key, double defaultValu
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dict_get_bool(dictionary@ data, const string&in key, bool defaultValue) {
|
bool dict_get_bool(dictionary @data, const string& in key, bool defaultValue) {
|
||||||
bool value = false;
|
bool value = false;
|
||||||
if (@data is null) return defaultValue;
|
if (@data is null)
|
||||||
if (data.get(key, value)) return value;
|
return defaultValue;
|
||||||
|
if (data.get(key, value))
|
||||||
|
return value;
|
||||||
|
|
||||||
int valueInt = 0;
|
int valueInt = 0;
|
||||||
if (data.get(key, valueInt)) return valueInt != 0;
|
if (data.get(key, valueInt))
|
||||||
|
return valueInt != 0;
|
||||||
|
|
||||||
string valueString = "";
|
string valueString = "";
|
||||||
if (data.get(key, valueString)) return valueString == "1" || valueString == "true";
|
if (data.get(key, valueString))
|
||||||
|
return valueString == "1" || valueString == "true";
|
||||||
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dict_has_keys(dictionary@ data) {
|
bool dict_has_keys(dictionary @data) {
|
||||||
if (@data is null) return false;
|
if (@data is null)
|
||||||
string[]@ keys = data.get_keys();
|
return false;
|
||||||
|
string[] @keys = data.get_keys();
|
||||||
return @keys !is null && keys.length() > 0;
|
return @keys !is null && keys.length() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dict_has_number_key(dictionary@ data, const string&in key) {
|
bool dict_has_number_key(dictionary @data, const string& in key) {
|
||||||
double value = 0.0;
|
double value = 0.0;
|
||||||
if (@data is null) return false;
|
if (@data is null)
|
||||||
if (data.get(key, value)) return true;
|
return false;
|
||||||
|
if (data.get(key, value))
|
||||||
|
return true;
|
||||||
|
|
||||||
int valueInt = 0;
|
int valueInt = 0;
|
||||||
if (data.get(key, valueInt)) return true;
|
if (data.get(key, valueInt))
|
||||||
|
return true;
|
||||||
|
|
||||||
string valueString = "";
|
string valueString = "";
|
||||||
if (data.get(key, valueString)) return valueString.length() > 0;
|
if (data.get(key, valueString))
|
||||||
|
return valueString.length() > 0;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] dict_get_string_list(dictionary@ data, const string&in key) {
|
string[] dict_get_string_list(dictionary @data, const string& in key) {
|
||||||
string[] result;
|
string[] result;
|
||||||
if (@data is null) return result;
|
if (@data is null)
|
||||||
if (!data.get(key, result)) return result;
|
return result;
|
||||||
|
if (!data.get(key, result))
|
||||||
|
return result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-24
@@ -1,11 +1,11 @@
|
|||||||
#include "audio_paths.nvgt"
|
#include "audio_paths.nvgt"
|
||||||
#include "file_viewer.nvgt"
|
#include "file_viewer.nvgt"
|
||||||
|
|
||||||
funcdef void docs_browser_speak_callback(const string &in message, bool interrupt);
|
funcdef void docs_browser_speak_callback(const string& in message, bool interrupt);
|
||||||
funcdef void docs_browser_tick_callback();
|
funcdef void docs_browser_tick_callback();
|
||||||
|
|
||||||
docs_browser_speak_callback@ docsBrowserSpeakCallback = null;
|
docs_browser_speak_callback @docsBrowserSpeakCallback = null;
|
||||||
docs_browser_tick_callback@ docsBrowserTickCallback = null;
|
docs_browser_tick_callback @docsBrowserTickCallback = null;
|
||||||
|
|
||||||
string docsBrowserDirectory = "files";
|
string docsBrowserDirectory = "files";
|
||||||
string docsBrowserMenuSoundDir = "sounds/menu";
|
string docsBrowserMenuSoundDir = "sounds/menu";
|
||||||
@@ -16,11 +16,11 @@ string[] docsBrowserExtensions = {"md", "MD", "txt", "TXT"};
|
|||||||
sound docsBrowserMoveSound;
|
sound docsBrowserMoveSound;
|
||||||
sound docsBrowserSelectSound;
|
sound docsBrowserSelectSound;
|
||||||
|
|
||||||
void docs_browser_set_speak_callback(docs_browser_speak_callback@ callback) {
|
void docs_browser_set_speak_callback(docs_browser_speak_callback @callback) {
|
||||||
@docsBrowserSpeakCallback = @callback;
|
@docsBrowserSpeakCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_set_tick_callback(docs_browser_tick_callback@ callback) {
|
void docs_browser_set_tick_callback(docs_browser_tick_callback @callback) {
|
||||||
@docsBrowserTickCallback = @callback;
|
@docsBrowserTickCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +36,10 @@ void docs_browser_set_wrap(bool wrap) {
|
|||||||
docsBrowserWrap = wrap;
|
docsBrowserWrap = wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_set_extensions(string[]@ extensions) {
|
void docs_browser_set_extensions(string[] @extensions) {
|
||||||
docsBrowserExtensions.resize(0);
|
docsBrowserExtensions.resize(0);
|
||||||
if (@extensions is null) return;
|
if (@extensions is null)
|
||||||
|
return;
|
||||||
for (uint extIndex = 0; extIndex < extensions.length(); extIndex++) {
|
for (uint extIndex = 0; extIndex < extensions.length(); extIndex++) {
|
||||||
docsBrowserExtensions.insert_last(extensions[extIndex]);
|
docsBrowserExtensions.insert_last(extensions[extIndex]);
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,7 @@ void docs_browser_reset_default_extensions() {
|
|||||||
docs_browser_set_extensions(defaults);
|
docs_browser_set_extensions(defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_speak(const string &in message, bool interrupt) {
|
void docs_browser_speak(const string& in message, bool interrupt) {
|
||||||
if (@docsBrowserSpeakCallback !is null) {
|
if (@docsBrowserSpeakCallback !is null) {
|
||||||
docsBrowserSpeakCallback(message, interrupt);
|
docsBrowserSpeakCallback(message, interrupt);
|
||||||
return;
|
return;
|
||||||
@@ -63,19 +64,20 @@ void docs_browser_tick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool docs_browser_sort_case_insensitive(const string &in a, const string &in b) {
|
bool docs_browser_sort_case_insensitive(const string& in a, const string& in b) {
|
||||||
return a.lower() < b.lower();
|
return a.lower() < b.lower();
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_append_unique_case_insensitive(string[]@ items, const string&in value) {
|
void docs_browser_append_unique_case_insensitive(string[] @items, const string& in value) {
|
||||||
string lowerValue = value.lower();
|
string lowerValue = value.lower();
|
||||||
for (uint itemIndex = 0; itemIndex < items.length(); itemIndex++) {
|
for (uint itemIndex = 0; itemIndex < items.length(); itemIndex++) {
|
||||||
if (items[itemIndex].lower() == lowerValue) return;
|
if (items[itemIndex].lower() == lowerValue)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
items.insert_last(value);
|
items.insert_last(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
string docs_browser_collapse_spaces(const string&in text) {
|
string docs_browser_collapse_spaces(const string& in text) {
|
||||||
string result = text;
|
string result = text;
|
||||||
while (result.find_first(" ") > -1) {
|
while (result.find_first(" ") > -1) {
|
||||||
result = result.replace(" ", " ", true);
|
result = result.replace(" ", " ", true);
|
||||||
@@ -83,7 +85,7 @@ string docs_browser_collapse_spaces(const string&in text) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
string docs_browser_format_label(const string&in filename) {
|
string docs_browser_format_label(const string& in filename) {
|
||||||
string name = filename;
|
string name = filename;
|
||||||
int dotPos = name.find_last_of(".");
|
int dotPos = name.find_last_of(".");
|
||||||
if (dotPos > 0) {
|
if (dotPos > 0) {
|
||||||
@@ -96,28 +98,33 @@ string docs_browser_format_label(const string&in filename) {
|
|||||||
name = name.lower();
|
name = name.lower();
|
||||||
name.trim_whitespace_this();
|
name.trim_whitespace_this();
|
||||||
|
|
||||||
if (name.length() == 0) return "Document";
|
if (name.length() == 0)
|
||||||
|
return "Document";
|
||||||
string first = name.substr(0, 1).upper();
|
string first = name.substr(0, 1).upper();
|
||||||
if (name.length() == 1) return first;
|
if (name.length() == 1)
|
||||||
|
return first;
|
||||||
return first + name.substr(1);
|
return first + name.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_collect_entries(string[]@ labels, string[]@ paths) {
|
void docs_browser_collect_entries(string[] @labels, string[] @paths) {
|
||||||
labels.resize(0);
|
labels.resize(0);
|
||||||
paths.resize(0);
|
paths.resize(0);
|
||||||
|
|
||||||
if (!directory_exists(docsBrowserDirectory)) return;
|
if (!directory_exists(docsBrowserDirectory))
|
||||||
|
return;
|
||||||
|
|
||||||
string[] docFiles;
|
string[] docFiles;
|
||||||
for (uint extIndex = 0; extIndex < docsBrowserExtensions.length(); extIndex++) {
|
for (uint extIndex = 0; extIndex < docsBrowserExtensions.length(); extIndex++) {
|
||||||
string ext = docsBrowserExtensions[extIndex];
|
string ext = docsBrowserExtensions[extIndex];
|
||||||
if (ext == "") continue;
|
if (ext == "")
|
||||||
|
continue;
|
||||||
if (ext.substr(0, 1) == ".") {
|
if (ext.substr(0, 1) == ".") {
|
||||||
ext = ext.substr(1);
|
ext = ext.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ found = find_files(docsBrowserDirectory + "/*." + ext);
|
string[] @found = find_files(docsBrowserDirectory + "/*." + ext);
|
||||||
if (@found is null) continue;
|
if (@found is null)
|
||||||
|
continue;
|
||||||
for (uint fileIndex = 0; fileIndex < found.length(); fileIndex++) {
|
for (uint fileIndex = 0; fileIndex < found.length(); fileIndex++) {
|
||||||
docs_browser_append_unique_case_insensitive(docFiles, found[fileIndex]);
|
docs_browser_append_unique_case_insensitive(docFiles, found[fileIndex]);
|
||||||
}
|
}
|
||||||
@@ -133,7 +140,7 @@ void docs_browser_collect_entries(string[]@ labels, string[]@ paths) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_add_entries(string[]@ labels, string[]@ paths, int[]@ types, const int typeValue = 1) {
|
void docs_browser_add_entries(string[] @labels, string[] @paths, int[] @types, const int typeValue = 1) {
|
||||||
string[] docLabels;
|
string[] docLabels;
|
||||||
string[] docPaths;
|
string[] docPaths;
|
||||||
docs_browser_collect_entries(docLabels, docPaths);
|
docs_browser_collect_entries(docLabels, docPaths);
|
||||||
@@ -145,12 +152,14 @@ void docs_browser_add_entries(string[]@ labels, string[]@ paths, int[]@ types, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void docs_browser_play_ui_sound(sound &inout soundObj, const string basePath) {
|
void docs_browser_play_ui_sound(sound& inout soundObj, const string basePath) {
|
||||||
string soundPath = resolve_audio_path(basePath);
|
string soundPath = resolve_audio_path(basePath);
|
||||||
if (soundPath == "") return;
|
if (soundPath == "")
|
||||||
|
return;
|
||||||
|
|
||||||
soundObj.close();
|
soundObj.close();
|
||||||
if (!soundObj.load(soundPath)) return;
|
if (!soundObj.load(soundPath))
|
||||||
|
return;
|
||||||
soundObj.play();
|
soundObj.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-9
@@ -6,15 +6,8 @@ string file_viewer(string content, string title = "Text Reader", bool readOnly =
|
|||||||
audio_form f;
|
audio_form f;
|
||||||
f.create_window(title, false, true);
|
f.create_window(title, false, true);
|
||||||
|
|
||||||
int textControl = f.create_input_box(
|
int textControl = f.create_input_box((readOnly ? "Document (read only)" : "Document (editable)"), content, "", 0,
|
||||||
(readOnly ? "Document (read only)" : "Document (editable)"),
|
readOnly, true, true);
|
||||||
content,
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
readOnly,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
int okButton = -1;
|
int okButton = -1;
|
||||||
int closeButton = -1;
|
int closeButton = -1;
|
||||||
|
|||||||
+119
-48
@@ -1,12 +1,12 @@
|
|||||||
#include "audio_paths.nvgt"
|
#include "audio_paths.nvgt"
|
||||||
|
|
||||||
funcdef void learn_sounds_speak_callback(const string &in message, bool interrupt);
|
funcdef void learn_sounds_speak_callback(const string& in message, bool interrupt);
|
||||||
funcdef void learn_sounds_tick_callback();
|
funcdef void learn_sounds_tick_callback();
|
||||||
funcdef void learn_sounds_setup_callback();
|
funcdef void learn_sounds_setup_callback();
|
||||||
|
|
||||||
learn_sounds_speak_callback@ learnSoundsSpeakCallback = null;
|
learn_sounds_speak_callback @learnSoundsSpeakCallback = null;
|
||||||
learn_sounds_tick_callback@ learnSoundsTickCallback = null;
|
learn_sounds_tick_callback @learnSoundsTickCallback = null;
|
||||||
learn_sounds_setup_callback@ learnSoundsSetupCallback = null;
|
learn_sounds_setup_callback @learnSoundsSetupCallback = null;
|
||||||
|
|
||||||
string[] learnSoundsSkipList;
|
string[] learnSoundsSkipList;
|
||||||
string[] learnSoundsDescriptionPaths;
|
string[] learnSoundsDescriptionPaths;
|
||||||
@@ -22,15 +22,15 @@ sound learnSoundsPreviewSound;
|
|||||||
sound learnSoundsMoveSound;
|
sound learnSoundsMoveSound;
|
||||||
sound learnSoundsSelectSound;
|
sound learnSoundsSelectSound;
|
||||||
|
|
||||||
void learn_sounds_set_speak_callback(learn_sounds_speak_callback@ callback) {
|
void learn_sounds_set_speak_callback(learn_sounds_speak_callback @callback) {
|
||||||
@learnSoundsSpeakCallback = @callback;
|
@learnSoundsSpeakCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_set_tick_callback(learn_sounds_tick_callback@ callback) {
|
void learn_sounds_set_tick_callback(learn_sounds_tick_callback @callback) {
|
||||||
@learnSoundsTickCallback = @callback;
|
@learnSoundsTickCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_set_setup_callback(learn_sounds_setup_callback@ callback) {
|
void learn_sounds_set_setup_callback(learn_sounds_setup_callback @callback) {
|
||||||
@learnSoundsSetupCallback = @callback;
|
@learnSoundsSetupCallback = @callback;
|
||||||
learnSoundsSetupApplied = false;
|
learnSoundsSetupApplied = false;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ void learn_sounds_reset_configuration() {
|
|||||||
learnSoundsSetupApplied = false;
|
learnSoundsSetupApplied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_speak(const string &in message, bool interrupt) {
|
void learn_sounds_speak(const string& in message, bool interrupt) {
|
||||||
if (@learnSoundsSpeakCallback !is null) {
|
if (@learnSoundsSpeakCallback !is null) {
|
||||||
learnSoundsSpeakCallback(message, interrupt);
|
learnSoundsSpeakCallback(message, interrupt);
|
||||||
return;
|
return;
|
||||||
@@ -107,33 +107,37 @@ void learn_sounds_apply_setup_once() {
|
|||||||
learnSoundsSetupApplied = true;
|
learnSoundsSetupApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string learn_sounds_normalize_path(const string&in path) {
|
string learn_sounds_normalize_path(const string& in path) {
|
||||||
return path.replace("\\", "/", true);
|
return path.replace("\\", "/", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool learn_sounds_is_directory_skip_entry(const string&in entry) {
|
bool learn_sounds_is_directory_skip_entry(const string& in entry) {
|
||||||
if (entry.length() == 0) return false;
|
if (entry.length() == 0)
|
||||||
if (entry.substr(entry.length() - 1) == "/") return true;
|
return false;
|
||||||
|
if (entry.substr(entry.length() - 1) == "/")
|
||||||
|
return true;
|
||||||
return directory_exists(entry);
|
return directory_exists(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool learn_sounds_should_skip_path(const string&in path) {
|
bool learn_sounds_should_skip_path(const string& in path) {
|
||||||
string normalizedPath = learn_sounds_normalize_path(path);
|
string normalizedPath = learn_sounds_normalize_path(path);
|
||||||
for (uint skipIndex = 0; skipIndex < learnSoundsSkipList.length(); skipIndex++) {
|
for (uint skipIndex = 0; skipIndex < learnSoundsSkipList.length(); skipIndex++) {
|
||||||
string entry = learn_sounds_normalize_path(learnSoundsSkipList[skipIndex]);
|
string entry = learn_sounds_normalize_path(learnSoundsSkipList[skipIndex]);
|
||||||
if (entry.length() == 0) continue;
|
if (entry.length() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
bool isDirectory = learn_sounds_is_directory_skip_entry(entry);
|
bool isDirectory = learn_sounds_is_directory_skip_entry(entry);
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
if (entry.substr(entry.length() - 1) != "/") entry += "/";
|
if (entry.substr(entry.length() - 1) != "/")
|
||||||
if (normalizedPath.length() >= entry.length() &&
|
entry += "/";
|
||||||
normalizedPath.substr(0, entry.length()) == entry) {
|
if (normalizedPath.length() >= entry.length() && normalizedPath.substr(0, entry.length()) == entry) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedPath == entry) return true;
|
if (normalizedPath == entry)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (entry.find_first("/") < 0 && entry.find_first("\\") < 0) {
|
if (entry.find_first("/") < 0 && entry.find_first("\\") < 0) {
|
||||||
if (normalizedPath.length() >= entry.length() + 1 &&
|
if (normalizedPath.length() >= entry.length() + 1 &&
|
||||||
@@ -145,7 +149,7 @@ bool learn_sounds_should_skip_path(const string&in path) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string learn_sounds_get_description(const string&in path) {
|
string learn_sounds_get_description(const string& in path) {
|
||||||
string normalizedPath = learn_sounds_normalize_path(path);
|
string normalizedPath = learn_sounds_normalize_path(path);
|
||||||
uint descriptionCount = learnSoundsDescriptionPaths.length();
|
uint descriptionCount = learnSoundsDescriptionPaths.length();
|
||||||
if (learnSoundsDescriptionTexts.length() < descriptionCount) {
|
if (learnSoundsDescriptionTexts.length() < descriptionCount) {
|
||||||
@@ -161,36 +165,99 @@ string learn_sounds_get_description(const string&in path) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_gather_files_recursive(const string&in basePath, string[]@ outFiles) {
|
bool learn_sounds_contains_path_ci(string[] @paths, const string& in path) {
|
||||||
string[]@ oggFiles = find_files(basePath + "/*.ogg");
|
string normalizedPath = learn_sounds_normalize_path(path).lower();
|
||||||
|
for (uint pathIndex = 0; pathIndex < paths.length(); pathIndex++) {
|
||||||
|
if (learn_sounds_normalize_path(paths[pathIndex]).lower() == normalizedPath) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void learn_sounds_add_path_if_missing(string[] @paths, const string& in path) {
|
||||||
|
if (learn_sounds_contains_path_ci(paths, path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paths.insert_last(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool learn_sounds_has_audio_extension(const string& in path) {
|
||||||
|
string lowerPath = path.lower();
|
||||||
|
if (lowerPath.length() < 4)
|
||||||
|
return false;
|
||||||
|
string suffix = lowerPath.substr(lowerPath.length() - 4);
|
||||||
|
return suffix == ".ogg" || suffix == ".wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
void learn_sounds_gather_files_from_pack(const string& in basePath, string[] @outFiles) {
|
||||||
|
pack @activePack = cast<pack @>(sound_default_pack);
|
||||||
|
if (@activePack is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string[] @packFiles = activePack.list_files();
|
||||||
|
if (@packFiles is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string normalizedBasePath = learn_sounds_normalize_path(basePath);
|
||||||
|
if (normalizedBasePath != "" && normalizedBasePath.substr(normalizedBasePath.length() - 1) != "/") {
|
||||||
|
normalizedBasePath += "/";
|
||||||
|
}
|
||||||
|
string normalizedBasePathLower = normalizedBasePath.lower();
|
||||||
|
|
||||||
|
for (uint fileIndex = 0; fileIndex < packFiles.length(); fileIndex++) {
|
||||||
|
string normalizedPath = learn_sounds_normalize_path(packFiles[fileIndex]);
|
||||||
|
if (normalizedPath.find("./") == 0) {
|
||||||
|
normalizedPath = normalizedPath.substr(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!learn_sounds_has_audio_extension(normalizedPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedBasePathLower != "") {
|
||||||
|
if (normalizedPath.length() < normalizedBasePathLower.length()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (normalizedPath.substr(0, normalizedBasePathLower.length()).lower() != normalizedBasePathLower) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
learn_sounds_add_path_if_missing(outFiles, normalizedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void learn_sounds_gather_files_recursive(const string& in basePath, string[] @outFiles) {
|
||||||
|
string[] @oggFiles = find_files(basePath + "/*.ogg");
|
||||||
if (@oggFiles !is null) {
|
if (@oggFiles !is null) {
|
||||||
for (uint fileIndex = 0; fileIndex < oggFiles.length(); fileIndex++) {
|
for (uint fileIndex = 0; fileIndex < oggFiles.length(); fileIndex++) {
|
||||||
outFiles.insert_last(basePath + "/" + oggFiles[fileIndex]);
|
learn_sounds_add_path_if_missing(outFiles, basePath + "/" + oggFiles[fileIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ oggUpperFiles = find_files(basePath + "/*.OGG");
|
string[] @oggUpperFiles = find_files(basePath + "/*.OGG");
|
||||||
if (@oggUpperFiles !is null) {
|
if (@oggUpperFiles !is null) {
|
||||||
for (uint fileIndex = 0; fileIndex < oggUpperFiles.length(); fileIndex++) {
|
for (uint fileIndex = 0; fileIndex < oggUpperFiles.length(); fileIndex++) {
|
||||||
outFiles.insert_last(basePath + "/" + oggUpperFiles[fileIndex]);
|
learn_sounds_add_path_if_missing(outFiles, basePath + "/" + oggUpperFiles[fileIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ wavFiles = find_files(basePath + "/*.wav");
|
string[] @wavFiles = find_files(basePath + "/*.wav");
|
||||||
if (@wavFiles !is null) {
|
if (@wavFiles !is null) {
|
||||||
for (uint fileIndex = 0; fileIndex < wavFiles.length(); fileIndex++) {
|
for (uint fileIndex = 0; fileIndex < wavFiles.length(); fileIndex++) {
|
||||||
outFiles.insert_last(basePath + "/" + wavFiles[fileIndex]);
|
learn_sounds_add_path_if_missing(outFiles, basePath + "/" + wavFiles[fileIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ wavUpperFiles = find_files(basePath + "/*.WAV");
|
string[] @wavUpperFiles = find_files(basePath + "/*.WAV");
|
||||||
if (@wavUpperFiles !is null) {
|
if (@wavUpperFiles !is null) {
|
||||||
for (uint fileIndex = 0; fileIndex < wavUpperFiles.length(); fileIndex++) {
|
for (uint fileIndex = 0; fileIndex < wavUpperFiles.length(); fileIndex++) {
|
||||||
outFiles.insert_last(basePath + "/" + wavUpperFiles[fileIndex]);
|
learn_sounds_add_path_if_missing(outFiles, basePath + "/" + wavUpperFiles[fileIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ folders = find_directories(basePath + "/*");
|
string[] @folders = find_directories(basePath + "/*");
|
||||||
if (@folders !is null) {
|
if (@folders !is null) {
|
||||||
for (uint folderIndex = 0; folderIndex < folders.length(); folderIndex++) {
|
for (uint folderIndex = 0; folderIndex < folders.length(); folderIndex++) {
|
||||||
learn_sounds_gather_files_recursive(basePath + "/" + folders[folderIndex], outFiles);
|
learn_sounds_gather_files_recursive(basePath + "/" + folders[folderIndex], outFiles);
|
||||||
@@ -198,11 +265,11 @@ void learn_sounds_gather_files_recursive(const string&in basePath, string[]@ out
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool learn_sounds_sort_case_insensitive(const string &in a, const string &in b) {
|
bool learn_sounds_sort_case_insensitive(const string& in a, const string& in b) {
|
||||||
return a.lower() < b.lower();
|
return a.lower() < b.lower();
|
||||||
}
|
}
|
||||||
|
|
||||||
string learn_sounds_collapse_spaces(const string&in text) {
|
string learn_sounds_collapse_spaces(const string& in text) {
|
||||||
string result = text;
|
string result = text;
|
||||||
while (result.find_first(" ") > -1) {
|
while (result.find_first(" ") > -1) {
|
||||||
result = result.replace(" ", " ", true);
|
result = result.replace(" ", " ", true);
|
||||||
@@ -210,7 +277,7 @@ string learn_sounds_collapse_spaces(const string&in text) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
string learn_sounds_label_from_path(const string&in soundPath) {
|
string learn_sounds_label_from_path(const string& in soundPath) {
|
||||||
string normalizedPath = learn_sounds_normalize_path(soundPath);
|
string normalizedPath = learn_sounds_normalize_path(soundPath);
|
||||||
int slashPos = normalizedPath.find_last_of("/");
|
int slashPos = normalizedPath.find_last_of("/");
|
||||||
string name = (slashPos >= 0) ? normalizedPath.substr(slashPos + 1) : normalizedPath;
|
string name = (slashPos >= 0) ? normalizedPath.substr(slashPos + 1) : normalizedPath;
|
||||||
@@ -228,20 +295,20 @@ string learn_sounds_label_from_path(const string&in soundPath) {
|
|||||||
name = name.lower();
|
name = name.lower();
|
||||||
name.trim_whitespace_this();
|
name.trim_whitespace_this();
|
||||||
|
|
||||||
if (name.length() == 0) return "Sound";
|
if (name.length() == 0)
|
||||||
|
return "Sound";
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_collect_entries(string[]@ labels, string[]@ soundPaths) {
|
void learn_sounds_collect_entries(string[] @labels, string[] @soundPaths) {
|
||||||
labels.resize(0);
|
labels.resize(0);
|
||||||
soundPaths.resize(0);
|
soundPaths.resize(0);
|
||||||
|
|
||||||
if (!directory_exists(learnSoundsRootDir)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] discoveredFiles;
|
string[] discoveredFiles;
|
||||||
|
if (directory_exists(learnSoundsRootDir)) {
|
||||||
learn_sounds_gather_files_recursive(learnSoundsRootDir, discoveredFiles);
|
learn_sounds_gather_files_recursive(learnSoundsRootDir, discoveredFiles);
|
||||||
|
}
|
||||||
|
learn_sounds_gather_files_from_pack(learnSoundsRootDir, discoveredFiles);
|
||||||
if (discoveredFiles.length() > 1) {
|
if (discoveredFiles.length() > 1) {
|
||||||
discoveredFiles.sort(learn_sounds_sort_case_insensitive);
|
discoveredFiles.sort(learn_sounds_sort_case_insensitive);
|
||||||
}
|
}
|
||||||
@@ -252,7 +319,7 @@ void learn_sounds_collect_entries(string[]@ labels, string[]@ soundPaths) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string label = learn_sounds_label_from_path(soundPath);
|
string label = i18n_text(learn_sounds_label_from_path(soundPath));
|
||||||
string description = learn_sounds_get_description(soundPath);
|
string description = learn_sounds_get_description(soundPath);
|
||||||
if (description.length() > 0) {
|
if (description.length() > 0) {
|
||||||
label += " - " + description;
|
label += " - " + description;
|
||||||
@@ -263,16 +330,24 @@ void learn_sounds_collect_entries(string[]@ labels, string[]@ soundPaths) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void learn_sounds_play_ui_sound(sound &inout soundObj, const string basePath) {
|
void learn_sounds_play_ui_sound(sound& inout soundObj, const string basePath) {
|
||||||
string soundPath = resolve_audio_path(basePath);
|
soundObj.close();
|
||||||
if (soundPath == "") {
|
|
||||||
|
// Avoid file_exists()-only path checks so packaged Android assets can still load.
|
||||||
|
if (soundObj.load(basePath)) {
|
||||||
|
soundObj.play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
soundObj.close();
|
if (soundObj.load(basePath + ".ogg")) {
|
||||||
if (!soundObj.load(soundPath)) {
|
soundObj.play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!soundObj.load(basePath + ".wav")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
soundObj.play();
|
soundObj.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,10 +399,6 @@ void learn_sounds_run_menu() {
|
|||||||
learn_sounds_play_ui_sound(learnSoundsSelectSound, learnSoundsMenuSoundDir + "/menu_select");
|
learn_sounds_play_ui_sound(learnSoundsSelectSound, learnSoundsMenuSoundDir + "/menu_select");
|
||||||
}
|
}
|
||||||
string selectedPath = soundPaths[selection];
|
string selectedPath = soundPaths[selection];
|
||||||
if (!file_exists(selectedPath)) {
|
|
||||||
learn_sounds_speak("Sound not found.", true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
learnSoundsPreviewSound.close();
|
learnSoundsPreviewSound.close();
|
||||||
if (!learnSoundsPreviewSound.load(selectedPath)) {
|
if (!learnSoundsPreviewSound.load(selectedPath)) {
|
||||||
|
|||||||
+61
-31
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
// Applies common menu sounds from a directory.
|
// Applies common menu sounds from a directory.
|
||||||
// Looks for both .ogg and .wav files automatically.
|
// Looks for both .ogg and .wav files automatically.
|
||||||
void menu_apply_default_sounds(menu@ menuRef, const string soundDir = "sounds/menu") {
|
void menu_apply_default_sounds(menu @menuRef, const string soundDir = "sounds/menu") {
|
||||||
if (@menuRef is null) {
|
if (@menuRef is null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,8 @@ void menu_apply_default_sounds(menu@ menuRef, const string soundDir = "sounds/me
|
|||||||
|
|
||||||
// Minimal blocking list menu.
|
// Minimal blocking list menu.
|
||||||
// Returns selected index or -1 on empty input/escape.
|
// Returns selected index or -1 on empty input/escape.
|
||||||
int menu_run_simple(const string introText, string[]@ options, bool wrap = true, int startIndex = 0, const string soundDir = "sounds/menu") {
|
int menu_run_simple(const string introText, string[] @options, bool wrap = true, int startIndex = 0,
|
||||||
|
const string soundDir = "sounds/menu") {
|
||||||
if (@options is null || options.length() == 0) {
|
if (@options is null || options.length() == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -39,37 +40,64 @@ int menu_run_simple(const string introText, string[]@ options, bool wrap = true,
|
|||||||
|
|
||||||
// Returns a-z for menu prefix filtering, or empty string when no letter key was pressed.
|
// Returns a-z for menu prefix filtering, or empty string when no letter key was pressed.
|
||||||
string menu_get_filter_letter() {
|
string menu_get_filter_letter() {
|
||||||
if (key_pressed(KEY_A)) return "a";
|
if (key_pressed(KEY_A))
|
||||||
if (key_pressed(KEY_B)) return "b";
|
return "a";
|
||||||
if (key_pressed(KEY_C)) return "c";
|
if (key_pressed(KEY_B))
|
||||||
if (key_pressed(KEY_D)) return "d";
|
return "b";
|
||||||
if (key_pressed(KEY_E)) return "e";
|
if (key_pressed(KEY_C))
|
||||||
if (key_pressed(KEY_F)) return "f";
|
return "c";
|
||||||
if (key_pressed(KEY_G)) return "g";
|
if (key_pressed(KEY_D))
|
||||||
if (key_pressed(KEY_H)) return "h";
|
return "d";
|
||||||
if (key_pressed(KEY_I)) return "i";
|
if (key_pressed(KEY_E))
|
||||||
if (key_pressed(KEY_J)) return "j";
|
return "e";
|
||||||
if (key_pressed(KEY_K)) return "k";
|
if (key_pressed(KEY_F))
|
||||||
if (key_pressed(KEY_L)) return "l";
|
return "f";
|
||||||
if (key_pressed(KEY_M)) return "m";
|
if (key_pressed(KEY_G))
|
||||||
if (key_pressed(KEY_N)) return "n";
|
return "g";
|
||||||
if (key_pressed(KEY_O)) return "o";
|
if (key_pressed(KEY_H))
|
||||||
if (key_pressed(KEY_P)) return "p";
|
return "h";
|
||||||
if (key_pressed(KEY_Q)) return "q";
|
if (key_pressed(KEY_I))
|
||||||
if (key_pressed(KEY_R)) return "r";
|
return "i";
|
||||||
if (key_pressed(KEY_S)) return "s";
|
if (key_pressed(KEY_J))
|
||||||
if (key_pressed(KEY_T)) return "t";
|
return "j";
|
||||||
if (key_pressed(KEY_U)) return "u";
|
if (key_pressed(KEY_K))
|
||||||
if (key_pressed(KEY_V)) return "v";
|
return "k";
|
||||||
if (key_pressed(KEY_W)) return "w";
|
if (key_pressed(KEY_L))
|
||||||
if (key_pressed(KEY_X)) return "x";
|
return "l";
|
||||||
if (key_pressed(KEY_Y)) return "y";
|
if (key_pressed(KEY_M))
|
||||||
if (key_pressed(KEY_Z)) return "z";
|
return "m";
|
||||||
|
if (key_pressed(KEY_N))
|
||||||
|
return "n";
|
||||||
|
if (key_pressed(KEY_O))
|
||||||
|
return "o";
|
||||||
|
if (key_pressed(KEY_P))
|
||||||
|
return "p";
|
||||||
|
if (key_pressed(KEY_Q))
|
||||||
|
return "q";
|
||||||
|
if (key_pressed(KEY_R))
|
||||||
|
return "r";
|
||||||
|
if (key_pressed(KEY_S))
|
||||||
|
return "s";
|
||||||
|
if (key_pressed(KEY_T))
|
||||||
|
return "t";
|
||||||
|
if (key_pressed(KEY_U))
|
||||||
|
return "u";
|
||||||
|
if (key_pressed(KEY_V))
|
||||||
|
return "v";
|
||||||
|
if (key_pressed(KEY_W))
|
||||||
|
return "w";
|
||||||
|
if (key_pressed(KEY_X))
|
||||||
|
return "x";
|
||||||
|
if (key_pressed(KEY_Y))
|
||||||
|
return "y";
|
||||||
|
if (key_pressed(KEY_Z))
|
||||||
|
return "z";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies a prefix filter to menu options.
|
// Applies a prefix filter to menu options.
|
||||||
void menu_apply_prefix_filter(const string &in filterText, const string[]@ options, int[]@ filteredIndices, string[]@ filteredOptions) {
|
void menu_apply_prefix_filter(const string& in filterText, const string[] @options, int[] @filteredIndices,
|
||||||
|
string[] @filteredOptions) {
|
||||||
filteredIndices.resize(0);
|
filteredIndices.resize(0);
|
||||||
filteredOptions.resize(0);
|
filteredOptions.resize(0);
|
||||||
|
|
||||||
@@ -86,7 +114,8 @@ void menu_apply_prefix_filter(const string &in filterText, const string[]@ optio
|
|||||||
}
|
}
|
||||||
|
|
||||||
string optionLower = options[optionIndex].lower();
|
string optionLower = options[optionIndex].lower();
|
||||||
if (optionLower.length() >= filterLower.length() && optionLower.substr(0, filterLower.length()) == filterLower) {
|
if (optionLower.length() >= filterLower.length() &&
|
||||||
|
optionLower.substr(0, filterLower.length()) == filterLower) {
|
||||||
filteredIndices.insert_last(optionIndex);
|
filteredIndices.insert_last(optionIndex);
|
||||||
filteredOptions.insert_last(options[optionIndex]);
|
filteredOptions.insert_last(options[optionIndex]);
|
||||||
}
|
}
|
||||||
@@ -94,7 +123,8 @@ void menu_apply_prefix_filter(const string &in filterText, const string[]@ optio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Updates filter text from keyboard input and reapplies filtering.
|
// Updates filter text from keyboard input and reapplies filtering.
|
||||||
bool menu_update_prefix_filter(string &inout filterText, const string[]@ options, int[]@ filteredIndices, string[]@ filteredOptions, int &inout selection) {
|
bool menu_update_prefix_filter(string& inout filterText, const string[] @options, int[] @filteredIndices,
|
||||||
|
string[] @filteredOptions, int& inout selection) {
|
||||||
bool filterChanged = false;
|
bool filterChanged = false;
|
||||||
|
|
||||||
if (key_pressed(KEY_BACK) && filterText.length() > 0) {
|
if (key_pressed(KEY_BACK) && filterText.length() > 0) {
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
#include "music.nvgt"
|
#include "music.nvgt"
|
||||||
|
|
||||||
// Resume paused menu music when possible, otherwise start a new track.
|
// Resume paused menu music when possible, otherwise start a new track.
|
||||||
void menu_music_resume_or_play(music_manager &inout manager, const string track, const int fadeInMs = 0) {
|
void menu_music_resume_or_play(music_manager& inout manager, const string track, const int fadeInMs = 0) {
|
||||||
if (manager.resume(fadeInMs)) {
|
if (manager.resume(fadeInMs)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ void menu_music_resume_or_play(music_manager &inout manager, const string track,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pause menu music. Blocking is enabled by default so fade-out fully completes.
|
// Pause menu music. Blocking is enabled by default so fade-out fully completes.
|
||||||
bool menu_music_pause(music_manager &inout manager, const int fadeOutMs = 0, const bool blocking = true) {
|
bool menu_music_pause(music_manager& inout manager, const int fadeOutMs = 0, const bool blocking = true) {
|
||||||
if (!manager.playing) {
|
if (!manager.playing) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,6 @@ bool menu_music_pause(music_manager &inout manager, const int fadeOutMs = 0, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop menu music and reset playback state.
|
// Stop menu music and reset playback state.
|
||||||
void menu_music_stop(music_manager &inout manager, const int fadeOutMs = 0, const bool blocking = false) {
|
void menu_music_stop(music_manager& inout manager, const int fadeOutMs = 0, const bool blocking = false) {
|
||||||
manager.stop(fadeOutMs, blocking);
|
manager.stop(fadeOutMs, blocking);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
// Multikey input helpers.
|
// Multikey input helpers.
|
||||||
bool check_key_down(array<int>@ keys) {
|
bool check_key_down(array<int> @keys) {
|
||||||
// True when at least one key in the set is currently down.
|
// True when at least one key in the set is currently down.
|
||||||
if (keys is null || keys.length() == 0) {
|
if (keys is null || keys.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -14,7 +14,7 @@ bool check_key_down(array<int>@ keys) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_all_keys(array<int>@ keys) {
|
bool check_all_keys(array<int> @keys) {
|
||||||
// True only when every key in the set is currently down.
|
// True only when every key in the set is currently down.
|
||||||
if (keys is null || keys.length() == 0) {
|
if (keys is null || keys.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+27
-8
@@ -26,23 +26,28 @@ string normalize_name_whitespace(string name) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_windows_reserved_filename(const string&in upperName) {
|
bool is_windows_reserved_filename(const string& in upperName) {
|
||||||
if (upperName == "CON" || upperName == "PRN" || upperName == "AUX" || upperName == "NUL") return true;
|
if (upperName == "CON" || upperName == "PRN" || upperName == "AUX" || upperName == "NUL")
|
||||||
|
return true;
|
||||||
|
|
||||||
if (upperName.length() == 4 && upperName.substr(0, 3) == "COM") {
|
if (upperName.length() == 4 && upperName.substr(0, 3) == "COM") {
|
||||||
int num = parse_int(upperName.substr(3));
|
int num = parse_int(upperName.substr(3));
|
||||||
if (num >= 1 && num <= 9) return true;
|
if (num >= 1 && num <= 9)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upperName.length() == 4 && upperName.substr(0, 3) == "LPT") {
|
if (upperName.length() == 4 && upperName.substr(0, 3) == "LPT") {
|
||||||
int num = parse_int(upperName.substr(3));
|
int num = parse_int(upperName.substr(3));
|
||||||
if (num >= 1 && num <= 9) return true;
|
if (num >= 1 && num <= 9)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string sanitize_filename_base(string name, const int maxLength = 40, const string fallback = "item") {
|
string sanitize_filename_base_with_reserved_prefix(string name, const int maxLength = 40,
|
||||||
|
const string fallback = "item",
|
||||||
|
const string reservedPrefix = "file_") {
|
||||||
string normalized = normalize_name_whitespace(name);
|
string normalized = normalize_name_whitespace(name);
|
||||||
string result = "";
|
string result = "";
|
||||||
bool lastSeparator = false;
|
bool lastSeparator = false;
|
||||||
@@ -90,12 +95,26 @@ string sanitize_filename_base(string name, const int maxLength = 40, const strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_windows_reserved_filename(upperName)) {
|
if (is_windows_reserved_filename(upperName)) {
|
||||||
result = "file_" + result;
|
string safePrefix = reservedPrefix;
|
||||||
|
if (safePrefix == "")
|
||||||
|
safePrefix = "file_";
|
||||||
|
result = safePrefix + result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
string build_filename_from_name(const string&in name, const string&in extension = ".dat", const int maxBaseLength = 40, const string fallback = "item") {
|
string sanitize_filename_base(string name, const int maxLength = 40, const string fallback = "item") {
|
||||||
return sanitize_filename_base(name, maxBaseLength, fallback) + extension;
|
return sanitize_filename_base_with_reserved_prefix(name, maxLength, fallback, "file_");
|
||||||
|
}
|
||||||
|
|
||||||
|
string build_filename_from_name_ex(const string& in name, const string& in extension = ".dat",
|
||||||
|
const int maxBaseLength = 40, const string fallback = "item",
|
||||||
|
const string reservedPrefix = "file_") {
|
||||||
|
return sanitize_filename_base_with_reserved_prefix(name, maxBaseLength, fallback, reservedPrefix) + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
string build_filename_from_name(const string& in name, const string& in extension = ".dat",
|
||||||
|
const int maxBaseLength = 40, const string fallback = "item") {
|
||||||
|
return build_filename_from_name_ex(name, extension, maxBaseLength, fallback, "file_");
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -1,6 +1,6 @@
|
|||||||
#include "audio_paths.nvgt"
|
#include "audio_paths.nvgt"
|
||||||
|
|
||||||
funcdef void notification_speak_callback(const string &in message, bool interrupt);
|
funcdef void notification_speak_callback(const string& in message, bool interrupt);
|
||||||
|
|
||||||
string[] notificationsHistory;
|
string[] notificationsHistory;
|
||||||
string[] notificationsQueue;
|
string[] notificationsQueue;
|
||||||
@@ -13,9 +13,9 @@ timer notificationsDelayTimer;
|
|||||||
sound notificationsSound;
|
sound notificationsSound;
|
||||||
bool notificationsWaitingForSound = false;
|
bool notificationsWaitingForSound = false;
|
||||||
string notificationsSoundPath = "sounds/notify";
|
string notificationsSoundPath = "sounds/notify";
|
||||||
notification_speak_callback@ notificationsSpeakCallback = null;
|
notification_speak_callback @notificationsSpeakCallback = null;
|
||||||
|
|
||||||
void notifications_set_speak_callback(notification_speak_callback@ callback) {
|
void notifications_set_speak_callback(notification_speak_callback @callback) {
|
||||||
@notificationsSpeakCallback = @callback;
|
@notificationsSpeakCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ void notifications_clear() {
|
|||||||
notificationsSound.close();
|
notificationsSound.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifications_speak(const string &in message, bool interrupt) {
|
void notifications_speak(const string& in message, bool interrupt) {
|
||||||
if (@notificationsSpeakCallback !is null) {
|
if (@notificationsSpeakCallback !is null) {
|
||||||
notificationsSpeakCallback(message, interrupt);
|
notificationsSpeakCallback(message, interrupt);
|
||||||
return;
|
return;
|
||||||
@@ -64,7 +64,7 @@ void notifications_speak(const string &in message, bool interrupt) {
|
|||||||
screen_reader_speak(message, interrupt);
|
screen_reader_speak(message, interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifications_enqueue(const string &in message) {
|
void notifications_enqueue(const string& in message) {
|
||||||
notificationsQueue.insert_last(message);
|
notificationsQueue.insert_last(message);
|
||||||
notificationsHistory.insert_last(message);
|
notificationsHistory.insert_last(message);
|
||||||
|
|
||||||
@@ -76,13 +76,22 @@ void notifications_enqueue(const string &in message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool notifications_try_play_sound() {
|
bool notifications_try_play_sound() {
|
||||||
string resolvedPath = resolve_audio_path(notificationsSoundPath);
|
notificationsSound.close();
|
||||||
if (resolvedPath == "") {
|
|
||||||
return false;
|
// Avoid file_exists()-only checks so packaged Android assets can still load.
|
||||||
|
if (notificationsSound.load(notificationsSoundPath)) {
|
||||||
|
notificationsSound.play();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationsSound.close();
|
string oggPath = notificationsSoundPath + ".ogg";
|
||||||
if (!notificationsSound.load(resolvedPath)) {
|
if (notificationsSound.load(oggPath)) {
|
||||||
|
notificationsSound.play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string wavPath = notificationsSoundPath + ".wav";
|
||||||
|
if (!notificationsSound.load(wavPath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +150,9 @@ void notifications_check_keys() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int position = notificationsCurrentIndex + 1;
|
int position = notificationsCurrentIndex + 1;
|
||||||
notifications_speak(notificationsHistory[notificationsCurrentIndex] + " " + position + " of " + notificationsHistory.length(), true);
|
notifications_speak(notificationsHistory[notificationsCurrentIndex] + " " + position + " of " +
|
||||||
|
notificationsHistory.length(),
|
||||||
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +170,9 @@ void notifications_check_keys() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int position = notificationsCurrentIndex + 1;
|
int position = notificationsCurrentIndex + 1;
|
||||||
notifications_speak(notificationsHistory[notificationsCurrentIndex] + " " + position + " of " + notificationsHistory.length(), true);
|
notifications_speak(notificationsHistory[notificationsCurrentIndex] + " " + position + " of " +
|
||||||
|
notificationsHistory.length(),
|
||||||
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-17
@@ -1,25 +1,26 @@
|
|||||||
// Generic save/file helpers.
|
// Generic save/file helpers.
|
||||||
|
|
||||||
bool save_utils_sort_string_case_insensitive(const string &in a, const string &in b) {
|
bool save_utils_sort_string_case_insensitive(const string& in a, const string& in b) {
|
||||||
return a.lower() < b.lower();
|
return a.lower() < b.lower();
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] list_files_with_extension(const string&in extension, const string&in directory = "") {
|
string[] list_files_with_extension(const string& in extension, const string& in directory = "") {
|
||||||
string[] result;
|
string[] result;
|
||||||
if (extension == "") return result;
|
if (extension == "")
|
||||||
|
return result;
|
||||||
|
|
||||||
string dirPrefix = directory;
|
string dirPrefix = directory;
|
||||||
if (dirPrefix != "" && dirPrefix.substr(dirPrefix.length() - 1) != "/") {
|
if (dirPrefix != "" && dirPrefix.substr(dirPrefix.length() - 1) != "/") {
|
||||||
dirPrefix += "/";
|
dirPrefix += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]@ items = glob(dirPrefix + "*" + extension);
|
string[] @items = glob(dirPrefix + "*" + extension);
|
||||||
if (@items is null) return result;
|
if (@items is null)
|
||||||
|
return result;
|
||||||
|
|
||||||
for (uint itemIndex = 0; itemIndex < items.length(); itemIndex++) {
|
for (uint itemIndex = 0; itemIndex < items.length(); itemIndex++) {
|
||||||
string item = items[itemIndex];
|
string item = items[itemIndex];
|
||||||
if (item.length() >= extension.length() &&
|
if (item.length() >= extension.length() && item.substr(item.length() - extension.length()) == extension) {
|
||||||
item.substr(item.length() - extension.length()) == extension) {
|
|
||||||
result.insert_last(item);
|
result.insert_last(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,20 +31,19 @@ string[] list_files_with_extension(const string&in extension, const string&in di
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_files_with_extension(const string&in extension, const string&in directory = "") {
|
bool has_files_with_extension(const string& in extension, const string& in directory = "") {
|
||||||
return list_files_with_extension(extension, directory).length() > 0;
|
return list_files_with_extension(extension, directory).length() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string strip_file_extension(const string&in filename, const string&in extension) {
|
string strip_file_extension(const string& in filename, const string& in extension) {
|
||||||
if (extension != "" &&
|
if (extension != "" && filename.length() >= extension.length() &&
|
||||||
filename.length() >= extension.length() &&
|
|
||||||
filename.substr(filename.length() - extension.length()) == extension) {
|
filename.substr(filename.length() - extension.length()) == extension) {
|
||||||
return filename.substr(0, filename.length() - extension.length());
|
return filename.substr(0, filename.length() - extension.length());
|
||||||
}
|
}
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool save_string_file(const string&in filename, const string&in data) {
|
bool save_string_file(const string& in filename, const string& in data) {
|
||||||
if (data.length() == 0) {
|
if (data.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ bool save_string_file(const string&in filename, const string&in data) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_string_file(const string&in filename, string&out data, const bool allowEmpty = false) {
|
bool read_string_file(const string& in filename, string& out data, const bool allowEmpty = false) {
|
||||||
file inFile;
|
file inFile;
|
||||||
if (!inFile.open(filename, "rb")) {
|
if (!inFile.open(filename, "rb")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -73,20 +73,21 @@ bool read_string_file(const string&in filename, string&out data, const bool allo
|
|||||||
return allowEmpty || data.length() > 0;
|
return allowEmpty || data.length() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string encrypt_string_aes(const string&in rawData, const string&in key) {
|
string encrypt_string_aes(const string& in rawData, const string& in key) {
|
||||||
return string_aes_encrypt(rawData, key);
|
return string_aes_encrypt(rawData, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
string decrypt_string_aes(const string&in encryptedData, const string&in key) {
|
string decrypt_string_aes(const string& in encryptedData, const string& in key) {
|
||||||
return string_aes_decrypt(encryptedData, key);
|
return string_aes_decrypt(encryptedData, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool save_encrypted_file(const string&in filename, const string&in rawData, const string&in key) {
|
bool save_encrypted_file(const string& in filename, const string& in rawData, const string& in key) {
|
||||||
string encryptedData = encrypt_string_aes(rawData, key);
|
string encryptedData = encrypt_string_aes(rawData, key);
|
||||||
return save_string_file(filename, encryptedData);
|
return save_string_file(filename, encryptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_encrypted_file(const string&in filename, const string&in key, string&out rawData, const bool allowPlaintextFallback = true) {
|
bool read_encrypted_file(const string& in filename, const string& in key, string& out rawData,
|
||||||
|
const bool allowPlaintextFallback = true) {
|
||||||
string encryptedData = "";
|
string encryptedData = "";
|
||||||
if (!read_string_file(filename, encryptedData, false)) {
|
if (!read_string_file(filename, encryptedData, false)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Show simple usage help.
|
||||||
|
print_usage() {
|
||||||
|
echo "Usage: scripts/format-nvgt.sh [path/to/file.nvgt ...]"
|
||||||
|
echo "Formats all tracked .nvgt files when no file paths are provided."
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir=""
|
||||||
|
repoRoot=""
|
||||||
|
filePath=""
|
||||||
|
formattedCount=0
|
||||||
|
targetFiles=()
|
||||||
|
|
||||||
|
# Help flag is optional and exits early without formatting.
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop immediately if clang-format is not installed.
|
||||||
|
if ! command -v clang-format >/dev/null 2>&1; then
|
||||||
|
echo "clang-format is required but was not found in PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve script location, then run from repo root so relative paths work.
|
||||||
|
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repoRoot="$(cd "${scriptDir}/.." && pwd)"
|
||||||
|
cd "${repoRoot}"
|
||||||
|
|
||||||
|
# We require the project style file to keep formatting consistent.
|
||||||
|
if [[ ! -f ".clang-format" ]]; then
|
||||||
|
echo "Missing .clang-format in repo root: ${repoRoot}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No args: format every tracked .nvgt file in git.
|
||||||
|
if [[ "$#" -eq 0 ]]; then
|
||||||
|
mapfile -t targetFiles < <(git ls-files "*.nvgt")
|
||||||
|
else
|
||||||
|
# Args provided: validate each file and format only those paths.
|
||||||
|
for filePath in "$@"; do
|
||||||
|
if [[ ! -f "${filePath}" ]]; then
|
||||||
|
echo "File not found: ${filePath}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "${filePath}" != *.nvgt ]]; then
|
||||||
|
echo "Only .nvgt files are supported: ${filePath}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
targetFiles+=("${filePath}")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#targetFiles[@]}" -eq 0 ]]; then
|
||||||
|
echo "No .nvgt files found to format."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force C++ parsing rules for NVGT while still using repo .clang-format.
|
||||||
|
for filePath in "${targetFiles[@]}"; do
|
||||||
|
clang-format -i --style=file --assume-filename=file.cpp "${filePath}"
|
||||||
|
formattedCount=$((formattedCount + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -n "Formatted ${formattedCount} "
|
||||||
|
if [[ ${formattedCount} -ne 1 ]]; then
|
||||||
|
echo "files."
|
||||||
|
else
|
||||||
|
echo "file."
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
+29
-10
@@ -5,6 +5,18 @@ string[] speechHistory;
|
|||||||
int speechHistoryMaxEntries = 10;
|
int speechHistoryMaxEntries = 10;
|
||||||
int speechHistoryCurrentIndex = -1;
|
int speechHistoryCurrentIndex = -1;
|
||||||
bool speechHistoryDeduplicate = true;
|
bool speechHistoryDeduplicate = true;
|
||||||
|
funcdef string speech_history_message_transform_callback(const string& in message);
|
||||||
|
speech_history_message_transform_callback @speechHistoryMessageTransform = null;
|
||||||
|
|
||||||
|
void speech_history_set_message_transform_callback(speech_history_message_transform_callback @callback) {
|
||||||
|
@speechHistoryMessageTransform = @callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
string speech_history_transform_message(const string& in message) {
|
||||||
|
if (@speechHistoryMessageTransform is null)
|
||||||
|
return message;
|
||||||
|
return speechHistoryMessageTransform(message);
|
||||||
|
}
|
||||||
|
|
||||||
void speech_history_set_max_entries(int maxEntries) {
|
void speech_history_set_max_entries(int maxEntries) {
|
||||||
if (maxEntries < 1) {
|
if (maxEntries < 1) {
|
||||||
@@ -32,7 +44,7 @@ void speech_history_set_deduplicate(bool deduplicate) {
|
|||||||
speechHistoryDeduplicate = deduplicate;
|
speechHistoryDeduplicate = deduplicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void speech_history_add(const string &in message) {
|
void speech_history_add(const string& in message) {
|
||||||
if (message == "") {
|
if (message == "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -54,47 +66,54 @@ void speech_history_add(const string &in message) {
|
|||||||
speechHistoryCurrentIndex = speechHistory.length() - 1;
|
speechHistoryCurrentIndex = speechHistory.length() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void speak_with_history(const string &in message, bool interrupt) {
|
void speak_with_history(const string& in message, bool interrupt) {
|
||||||
speech_history_add(message);
|
string transformedMessage = speech_history_transform_message(message);
|
||||||
screen_reader_speak(message, interrupt);
|
speech_history_add(transformedMessage);
|
||||||
|
screen_reader_speak(transformedMessage, interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_speech_history_keys() {
|
void check_speech_history_keys() {
|
||||||
// Comma: older.
|
// Comma: older.
|
||||||
if (key_pressed(KEY_COMMA)) {
|
if (key_pressed(KEY_COMMA)) {
|
||||||
if (speechHistory.length() == 0) {
|
if (speechHistory.length() == 0) {
|
||||||
screen_reader_speak("No speech history.", true);
|
screen_reader_speak(speech_history_transform_message("No speech history."), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
speechHistoryCurrentIndex--;
|
speechHistoryCurrentIndex--;
|
||||||
if (speechHistoryCurrentIndex < 0) {
|
if (speechHistoryCurrentIndex < 0) {
|
||||||
speechHistoryCurrentIndex = 0;
|
speechHistoryCurrentIndex = 0;
|
||||||
screen_reader_speak("Oldest message. " + speechHistory[speechHistoryCurrentIndex], true);
|
screen_reader_speak(
|
||||||
|
speech_history_transform_message("Oldest message. " + speechHistory[speechHistoryCurrentIndex]), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = speechHistoryCurrentIndex + 1;
|
int position = speechHistoryCurrentIndex + 1;
|
||||||
screen_reader_speak(speechHistory[speechHistoryCurrentIndex] + " " + position + " of " + speechHistory.length(), true);
|
screen_reader_speak(speech_history_transform_message(speechHistory[speechHistoryCurrentIndex] + " " + position +
|
||||||
|
" of " + speechHistory.length()),
|
||||||
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Period: newer.
|
// Period: newer.
|
||||||
if (key_pressed(KEY_PERIOD)) {
|
if (key_pressed(KEY_PERIOD)) {
|
||||||
if (speechHistory.length() == 0) {
|
if (speechHistory.length() == 0) {
|
||||||
screen_reader_speak("No speech history.", true);
|
screen_reader_speak(speech_history_transform_message("No speech history."), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
speechHistoryCurrentIndex++;
|
speechHistoryCurrentIndex++;
|
||||||
if (speechHistoryCurrentIndex >= int(speechHistory.length())) {
|
if (speechHistoryCurrentIndex >= int(speechHistory.length())) {
|
||||||
speechHistoryCurrentIndex = speechHistory.length() - 1;
|
speechHistoryCurrentIndex = speechHistory.length() - 1;
|
||||||
screen_reader_speak("Newest message. " + speechHistory[speechHistoryCurrentIndex], true);
|
screen_reader_speak(
|
||||||
|
speech_history_transform_message("Newest message. " + speechHistory[speechHistoryCurrentIndex]), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = speechHistoryCurrentIndex + 1;
|
int position = speechHistoryCurrentIndex + 1;
|
||||||
screen_reader_speak(speechHistory[speechHistoryCurrentIndex] + " " + position + " of " + speechHistory.length(), true);
|
screen_reader_speak(speech_history_transform_message(speechHistory[speechHistoryCurrentIndex] + " " + position +
|
||||||
|
" of " + speechHistory.length()),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
string uiDefaultWindowTitle = "";
|
string uiDefaultWindowTitle = "";
|
||||||
bool uiUsePromptAsDialogTitle = true;
|
bool uiUsePromptAsDialogTitle = true;
|
||||||
|
funcdef string ui_text_transform_callback(const string& in text);
|
||||||
|
ui_text_transform_callback @uiTextTransformCallback = null;
|
||||||
|
|
||||||
|
void ui_set_text_transform_callback(ui_text_transform_callback @callback) {
|
||||||
|
@uiTextTransformCallback = @callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ui_transform_text(const string& in text) {
|
||||||
|
if (@uiTextTransformCallback is null)
|
||||||
|
return text;
|
||||||
|
return uiTextTransformCallback(text);
|
||||||
|
}
|
||||||
|
|
||||||
void ui_set_default_window_title(const string windowTitle) {
|
void ui_set_default_window_title(const string windowTitle) {
|
||||||
uiDefaultWindowTitle = windowTitle;
|
uiDefaultWindowTitle = windowTitle;
|
||||||
@@ -34,21 +46,26 @@ void ui_restore_window(const string windowTitle = "") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string ui_input_box(const string title, const string prompt, const string defaultValue = "", const string windowTitle = "") {
|
string ui_input_box(const string title, const string prompt, const string defaultValue = "",
|
||||||
string dialogTitle = ui_resolve_dialog_title(title, prompt);
|
const string windowTitle = "") {
|
||||||
string result = virtual_input_box(dialogTitle, prompt, defaultValue);
|
string transformedTitle = ui_transform_text(title);
|
||||||
|
string transformedPrompt = ui_transform_text(prompt);
|
||||||
|
string dialogTitle = ui_resolve_dialog_title(transformedTitle, transformedPrompt);
|
||||||
|
string result = virtual_input_box(dialogTitle, transformedPrompt, defaultValue);
|
||||||
ui_restore_window(windowTitle);
|
ui_restore_window(windowTitle);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ui_question(const string title, const string prompt, const string windowTitle = "", const bool canCancel = false) {
|
int ui_question(const string title, const string prompt, const string windowTitle = "", const bool canCancel = false) {
|
||||||
string dialogTitle = ui_resolve_dialog_title(title, prompt);
|
string transformedTitle = ui_transform_text(title);
|
||||||
int result = virtual_question(dialogTitle, prompt, canCancel);
|
string transformedPrompt = ui_transform_text(prompt);
|
||||||
|
string dialogTitle = ui_resolve_dialog_title(transformedTitle, transformedPrompt);
|
||||||
|
int result = virtual_question(dialogTitle, transformedPrompt, canCancel);
|
||||||
ui_restore_window(windowTitle);
|
ui_restore_window(windowTitle);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ui_info_box(const string title, const string heading, const string message, const string windowTitle = "") {
|
void ui_info_box(const string title, const string heading, const string message, const string windowTitle = "") {
|
||||||
virtual_info_box(title, heading, message);
|
virtual_info_box(ui_transform_text(title), ui_transform_text(heading), ui_transform_text(message));
|
||||||
ui_restore_window(windowTitle);
|
ui_restore_window(windowTitle);
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-10
@@ -6,9 +6,9 @@ float volumeControlsMaxDb = 0.0f;
|
|||||||
float volumeControlsMinDb = -60.0f;
|
float volumeControlsMinDb = -60.0f;
|
||||||
float volumeControlsStepDb = 3.0f;
|
float volumeControlsStepDb = 3.0f;
|
||||||
float volumeControlsCurrentDb = 0.0f;
|
float volumeControlsCurrentDb = 0.0f;
|
||||||
volume_controls_apply_callback@ volumeControlsApplyCallback = null;
|
volume_controls_apply_callback @volumeControlsApplyCallback = null;
|
||||||
|
|
||||||
void volume_controls_set_apply_callback(volume_controls_apply_callback@ callback) {
|
void volume_controls_set_apply_callback(volume_controls_apply_callback @callback) {
|
||||||
@volumeControlsApplyCallback = @callback;
|
@volumeControlsApplyCallback = @callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +31,15 @@ void volume_controls_configure(float minDb = -60.0f, float maxDb = 0.0f, float s
|
|||||||
|
|
||||||
int volume_controls_percent_from_db(float volumeDb) {
|
int volume_controls_percent_from_db(float volumeDb) {
|
||||||
float range = volumeControlsMaxDb - volumeControlsMinDb;
|
float range = volumeControlsMaxDb - volumeControlsMinDb;
|
||||||
if (range <= 0.0f) return 100;
|
if (range <= 0.0f)
|
||||||
|
return 100;
|
||||||
|
|
||||||
float normalized = (volumeDb - volumeControlsMinDb) / range;
|
float normalized = (volumeDb - volumeControlsMinDb) / range;
|
||||||
int volumePercent = int(normalized * 100.0f + 0.5f);
|
int volumePercent = int(normalized * 100.0f + 0.5f);
|
||||||
if (volumePercent < 0) volumePercent = 0;
|
if (volumePercent < 0)
|
||||||
if (volumePercent > 100) volumePercent = 100;
|
volumePercent = 0;
|
||||||
|
if (volumePercent > 100)
|
||||||
|
volumePercent = 100;
|
||||||
return volumePercent;
|
return volumePercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +55,13 @@ void volume_controls_apply(float volumeDb) {
|
|||||||
|
|
||||||
void volume_controls_set_current_db(float volumeDb, bool announce = true) {
|
void volume_controls_set_current_db(float volumeDb, bool announce = true) {
|
||||||
float clamped = volumeDb;
|
float clamped = volumeDb;
|
||||||
if (clamped > volumeControlsMaxDb) clamped = volumeControlsMaxDb;
|
if (clamped > volumeControlsMaxDb)
|
||||||
if (clamped < volumeControlsMinDb) clamped = volumeControlsMinDb;
|
clamped = volumeControlsMaxDb;
|
||||||
|
if (clamped < volumeControlsMinDb)
|
||||||
|
clamped = volumeControlsMinDb;
|
||||||
|
|
||||||
if (clamped == volumeControlsCurrentDb) return;
|
if (clamped == volumeControlsCurrentDb)
|
||||||
|
return;
|
||||||
|
|
||||||
volumeControlsCurrentDb = clamped;
|
volumeControlsCurrentDb = clamped;
|
||||||
volume_controls_apply(volumeControlsCurrentDb);
|
volume_controls_apply(volumeControlsCurrentDb);
|
||||||
@@ -83,8 +89,9 @@ void volume_controls_handle_keys(int downKey = KEY_PAGEDOWN, int upKey = KEY_PAG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void safe_destroy_sound_in_pool(sound_pool@ poolRef, int &inout handle) {
|
void safe_destroy_sound_in_pool(sound_pool @poolRef, int& inout handle) {
|
||||||
if (handle == -1) return;
|
if (handle == -1)
|
||||||
|
return;
|
||||||
if (@poolRef is null) {
|
if (@poolRef is null) {
|
||||||
handle = -1;
|
handle = -1;
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user