├── .gitattributes ├── .github ├── FUNDING.yml ├── actions │ ├── export-godot-project │ │ └── action.yml │ ├── make-release │ │ ├── action.yml │ │ └── release-notes.md │ ├── setup-deps │ │ └── action.yml │ ├── sign-godot-project │ │ ├── action.yml │ │ └── macos │ │ │ ├── setup.sh │ │ │ └── sign.sh │ └── zip-folder │ │ ├── action.yml │ │ └── zip.sh └── workflows │ ├── build-documentation.yml │ ├── build-pull-request.yml │ ├── build-release-tagged.yml │ ├── build-unstable.yml │ ├── export-project.yml │ └── publish-project.yml ├── .gitignore ├── LICENSE ├── Main.gd ├── Main.tscn ├── README.md ├── addons └── bosca_exports │ ├── BoscaWebExportPlugin.gd │ ├── plugin.cfg │ └── plugin.gd ├── assets ├── fonts │ ├── FFF Aquarius - License.txt │ ├── fff-aquarius-bold-condensed.msdf.ttf │ ├── fff-aquarius-bold-condensed.msdf.ttf.import │ ├── fff-aquarius-bold-condensed.normal.ttf │ └── fff-aquarius-bold-condensed.normal.ttf.import ├── icons │ ├── arrow_down.png │ ├── arrow_down.png.import │ ├── arrow_down_small.png │ ├── arrow_down_small.png.import │ ├── arrow_left.png │ ├── arrow_left.png.import │ ├── arrow_left_small.png │ ├── arrow_left_small.png.import │ ├── arrow_right.png │ ├── arrow_right.png.import │ ├── arrow_right_small.png │ ├── arrow_right_small.png.import │ ├── arrow_up.png │ ├── arrow_up.png.import │ ├── arrow_up_small.png │ ├── arrow_up_small.png.import │ ├── arrow_updown.png │ ├── arrow_updown.png.import │ ├── cross.png │ ├── cross.png.import │ ├── fullscreen_off.png │ ├── fullscreen_off.png.import │ ├── fullscreen_on.png │ ├── fullscreen_on.png.import │ ├── minus.png │ ├── minus.png.import │ ├── pause.png │ ├── pause.png.import │ ├── play.png │ ├── play.png.import │ ├── plus.png │ ├── plus.png.import │ ├── random.png │ ├── random.png.import │ ├── record.png │ ├── record.png.import │ ├── stop.png │ └── stop.png.import ├── logos │ ├── logo_blue.png │ ├── logo_blue.png.import │ ├── logo_cyan.png │ ├── logo_cyan.png.import │ ├── logo_gray.png │ ├── logo_gray.png.import │ ├── logo_green.png │ ├── logo_green.png.import │ ├── logo_orange.png │ ├── logo_orange.png.import │ ├── logo_purple.png │ ├── logo_purple.png.import │ ├── logo_red.png │ ├── logo_red.png.import │ ├── logo_shadow.png │ └── logo_shadow.png.import ├── overlap-dither.png └── overlap-dither.png.import ├── default_bus_layout.tres ├── dist ├── logo-full.png ├── logo-full.png.import ├── web-shell.html ├── web_assets │ ├── .gdignore │ ├── boscaweb.main.js │ ├── boscaweb.patches.js │ ├── logo.png │ ├── og_image.png │ └── styles.css └── windows-icon.ico ├── docs ├── .gdignore ├── .gitignore ├── README.md ├── assets │ ├── favicon.png │ ├── images │ │ ├── arrangements-dock-delete.png │ │ ├── arrangements-dock.png │ │ ├── arrangements-drag-n-drop.png │ │ ├── arrangements-patterns.png │ │ ├── arrangements-timeline-select.png │ │ ├── effects-swing.png │ │ ├── instruments-categories.png │ │ ├── instruments-dock-delete.png │ │ ├── instruments-pattern-picker.png │ │ ├── instruments-recording-values.png │ │ ├── instruments-recording.png │ │ ├── instruments-tuning-pads.png │ │ ├── introduction-welcome.png │ │ ├── io-export.png │ │ ├── io-import.png │ │ ├── overview-advanced-view.png │ │ ├── overview-arrangement-view.png │ │ ├── overview-builtin-guide.png │ │ ├── overview-file-view.png │ │ ├── overview-instrument-view.png │ │ ├── overview-pattern-editor-drums.png │ │ ├── overview-pattern-editor.png │ │ ├── patterns-drawing.png │ │ ├── patterns-drumkits.png │ │ ├── patterns-instrument.png │ │ ├── patterns-length.png │ │ ├── patterns-placing-notes.png │ │ ├── patterns-removing-notes.png │ │ ├── patterns-scale-key.png │ │ └── patterns-stamping.png │ ├── logo.png │ ├── og_image.png │ └── styles │ │ ├── normalize.css │ │ └── theme.css ├── build.py ├── requirements.txt ├── src │ ├── arrangements.md │ ├── community.md │ ├── effects.md │ ├── export_import.md │ ├── index.md │ ├── instruments.md │ ├── notes_and_patterns.md │ ├── overview.md │ └── shortcuts.md ├── templates │ └── article.html └── tools │ ├── colorize.py │ └── paths.py ├── editorconfig ├── enums ├── ColorPalette.gd ├── Effect.gd ├── Note.gd └── Scale.gd ├── export_presets.cfg ├── globals ├── Controller.gd ├── DebugManager.gd ├── HelpManager.gd ├── IOManager.gd ├── MusicPlayer.gd ├── PopupManager.gd ├── SettingsManager.gd ├── StateManager.gd ├── VoiceManager.gd └── WindowManager.gd ├── gui ├── MainWindow.gd ├── Menu.gd ├── Menu.tscn ├── theme │ ├── instruments │ │ ├── instrument_theme_blue.tres │ │ ├── instrument_theme_cyan.tres │ │ ├── instrument_theme_gray.tres │ │ ├── instrument_theme_green.tres │ │ ├── instrument_theme_orange.tres │ │ ├── instrument_theme_purple.tres │ │ └── instrument_theme_red.tres │ ├── navigation_buttons.tres │ ├── pad_slider_pattern.tres │ └── project_theme.tres ├── views │ ├── AdvancedView.gd │ ├── AdvancedView.tscn │ ├── ArrangementView.gd │ ├── ArrangementView.tscn │ ├── FileView.gd │ ├── FileView.tscn │ ├── GeneralHelpView.gd │ ├── GeneralHelpView.tscn │ ├── InstrumentView.gd │ ├── InstrumentView.tscn │ ├── LockedIndicator.gd │ ├── LockedIndicator.gdshader │ ├── LockedIndicator.tscn │ ├── PatternEditor.gd │ ├── PatternEditor.tscn │ ├── help_view │ │ ├── BackButton.gd │ │ ├── BackButton.tscn │ │ ├── ContributorLine.gd │ │ ├── ContributorLine.tscn │ │ ├── ShortcutLine.gd │ │ └── ShortcutLine.tscn │ ├── instrument_view │ │ ├── InstrumentDock.gd │ │ ├── InstrumentSettings.gd │ │ └── InstrumentSettings.tscn │ ├── note_map │ │ ├── NoteMap.gd │ │ ├── NoteMap.tscn │ │ ├── NoteMapGutter.gd │ │ ├── NoteMapOverlay.gd │ │ └── NoteMapScrollbar.gd │ └── pattern_map │ │ ├── PatternDock.gd │ │ ├── PatternMap.gd │ │ ├── PatternMap.tscn │ │ ├── PatternMapItems.gd │ │ ├── PatternMapOverlay.gd │ │ ├── PatternMapScrollbar.gd │ │ ├── PatternMapTimeline.gd │ │ └── PatternMapTrack.gd └── widgets │ ├── AccentedContentEffect.gd │ ├── AnimatedLogo.gd │ ├── AnimatedLogo.tscn │ ├── ButtonHolder.gd │ ├── DeleteArea.gd │ ├── DeleteArea.tscn │ ├── FilePathLabel.gd │ ├── FilePathLabel.tscn │ ├── FillerControl.gd │ ├── HighlightManager.gd │ ├── ItemDock.gd │ ├── ItemDock.tscn │ ├── LinkLabel.gd │ ├── LinkLabel.tscn │ ├── NavigationButton.gd │ ├── NavigationButton.tscn │ ├── OptionListPopup.gd │ ├── OptionPicker.gd │ ├── OptionPicker.tscn │ ├── PadSlider.gd │ ├── PadSlider.tscn │ ├── SquishyButton.gd │ ├── SquishyButton.tscn │ ├── Stepper.gd │ ├── Stepper.tscn │ ├── ToastMessage.gd │ ├── ToastMessage.tscn │ └── popups │ ├── CreditsPopup.gd │ ├── CreditsPopup.tscn │ ├── ExportMasterPopup.gd │ ├── ExportMasterPopup.tscn │ ├── ImportMasterPopup.gd │ ├── ImportMasterPopup.tscn │ ├── InfoPopup.gd │ ├── InfoPopup.tscn │ ├── ShortcutHelpPopup.tscn │ ├── WindowPopup.gd │ └── WindowPopup.tscn ├── help ├── advanced_guide.tres ├── advanced_guide_delete_instrument.png ├── advanced_guide_delete_instrument.png.import ├── advanced_guide_note_selection.png ├── advanced_guide_note_selection.png.import ├── advanced_guide_rec_filter.png ├── advanced_guide_rec_filter.png.import ├── basic_guide.tres ├── basic_guide_longnote.png ├── basic_guide_longnote.png.import ├── basic_guide_pattern_drag.png ├── basic_guide_pattern_drag.png.import ├── basic_guide_pattern_paint.png ├── basic_guide_pattern_paint.png.import ├── basic_guide_timeline_span.png └── basic_guide_timeline_span.png.import ├── icon.png ├── icon.png.import ├── io ├── MMLExporter.gd ├── MidiExporter.gd ├── MidiImporter.gd ├── SongLoader.gd ├── SongMerger.gd ├── SongSaver.gd ├── WavExporter.gd ├── XMExporter.gd └── midi │ ├── MidiFile.gd │ ├── MidiTrack.gd │ └── MidiTrackEvent.gd ├── objects ├── Arrangement.gd ├── DrumkitInstrument.gd ├── HelpGuide.gd ├── HelpGuideStep.gd ├── Instrument.gd ├── Pattern.gd ├── SingleVoiceInstrument.gd └── Song.gd ├── project.godot └── utils ├── ByteArrayUtil.gd ├── FileDialogNativeWeb.gd ├── FileWrapper.gd ├── HelpTester.gd ├── HelpTester.tscn └── ValueValidator.gd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: YuriSizov 2 | -------------------------------------------------------------------------------- /.github/actions/export-godot-project/action.yml: -------------------------------------------------------------------------------- 1 | name: Export Godot project 2 | description: Export a project for the target platform. 3 | 4 | inputs: 5 | platform: 6 | required: true 7 | arch: 8 | required: true 9 | preset: 10 | required: true 11 | output: 12 | required: true 13 | 14 | project-path: 15 | default: "." 16 | 17 | outputs: 18 | export-path: 19 | value: ${{ steps.export-project-step.outputs.export-path }} 20 | 21 | runs: 22 | using: "composite" 23 | steps: 24 | - name: Import assets, scripts, extensions 25 | shell: bash 26 | continue-on-error: true 27 | run: | 28 | godot --headless --path ${{ inputs.project-path }} --import 29 | 30 | - name: Export the project (${{ inputs.preset }}) 31 | id: export-project-step 32 | shell: bash 33 | env: 34 | EXPORT_OUTPUT_PATH: export/${{ inputs.platform }}/${{ inputs.arch }} 35 | run: | 36 | echo "Creating the export output folder..." 37 | mkdir -p ${{ inputs.project-path }}/${{ env.EXPORT_OUTPUT_PATH }} 38 | 39 | echo "Exporting the project..." 40 | godot --headless --path ${{ inputs.project-path }} --export-release "${{ inputs.preset }}" ${{ env.EXPORT_OUTPUT_PATH }}/${{ inputs.output }} 41 | echo "export-path=${{ inputs.project-path }}/${{ env.EXPORT_OUTPUT_PATH }}" >> "$GITHUB_OUTPUT" 42 | 43 | # Perform post-export steps. 44 | 45 | # We need the .app folder on macOS, not the zip that Godot produces. 46 | - name: Unzip the project (macos) 47 | if: ${{ inputs.platform == 'macos' }} 48 | shell: bash 49 | env: 50 | EXPORT_OUTPUT_PATH: export/${{ inputs.platform }}/${{ inputs.arch }} 51 | run: | 52 | cd ${{ inputs.project-path }}/${{ env.EXPORT_OUTPUT_PATH }} 53 | unzip ${{ inputs.output }} 54 | rm -f ${{ inputs.output }} 55 | -------------------------------------------------------------------------------- /.github/actions/make-release/action.yml: -------------------------------------------------------------------------------- 1 | name: Make GitHub Release 2 | description: Create a GitHub release as a draft, and generate its description. 3 | 4 | inputs: 5 | release-version: 6 | required: true 7 | 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Prepare release notes for this release 12 | shell: bash 13 | run: | 14 | sed -i 's/\${COMMIT_HASH}/${{ github.sha }}/g' $GITHUB_ACTION_PATH/release-notes.md 15 | sed -i 's/\${VERSION_TAG}/${{ inputs.release-version }}/g' $GITHUB_ACTION_PATH/release-notes.md 16 | 17 | - name: Create a draft release with custom release notes 18 | shell: bash 19 | env: 20 | GH_TOKEN: ${{ github.token }} 21 | run: | 22 | gh release create --verify-tag ${{ inputs.release-version }} --draft --title 'Bosca Ceoil Blue - ${{ inputs.release-version }}' --notes-file $GITHUB_ACTION_PATH/release-notes.md 23 | -------------------------------------------------------------------------------- /.github/actions/make-release/release-notes.md: -------------------------------------------------------------------------------- 1 | If you experience issues, [please report them](https://github.com/YuriSizov/boscaceoil-blue/issues) as soon as you can. 2 | 3 | ## Downloads 4 | 5 | * **[Download for Linux (x86_64)](https://github.com/YuriSizov/boscaceoil-blue/releases/download/${VERSION_TAG}/boscaceoil-blue-linux-x86_64.zip)** 6 | * **[Download for macOS (Universal)](https://github.com/YuriSizov/boscaceoil-blue/releases/download/${VERSION_TAG}/boscaceoil-blue-macos-universal.zip)** 7 | * **[Download for Windows (x86_64)](https://github.com/YuriSizov/boscaceoil-blue/releases/download/${VERSION_TAG}/boscaceoil-blue-windows-x86_64.zip)** 8 | * **[Download for Windows (x86_32)](https://github.com/YuriSizov/boscaceoil-blue/releases/download/${VERSION_TAG}/boscaceoil-blue-windows-x86_32.zip)** 9 | * [Download for web (self-hosting)](https://github.com/YuriSizov/boscaceoil-blue/releases/download/${VERSION_TAG}/boscaceoil-blue-web-universal.zip) 10 | 11 | _Built from commit [${COMMIT_HASH}](https://github.com/YuriSizov/boscaceoil-blue/commits/${COMMIT_HASH}/)._ 12 | -------------------------------------------------------------------------------- /.github/actions/setup-deps/action.yml: -------------------------------------------------------------------------------- 1 | name: Set up Project Dependencies 2 | description: Download and install dependencies and libraries used by the project. 3 | 4 | inputs: 5 | platform: 6 | required: true 7 | gdsion-version: 8 | required: true 9 | 10 | runs: 11 | using: "composite" 12 | steps: 13 | - name: Install rcedit (Windows) 14 | if: ${{ inputs.platform == 'windows' }} 15 | shell: bash 16 | run: | 17 | curl -JLO https://github.com/electron/rcedit/releases/download/v2.0.0/rcedit-x64.exe 18 | mv rcedit-x64.exe rcedit.exe 19 | export PATH=$(realpath ./):$PATH 20 | 21 | - name: Install GDSiON 22 | shell: bash 23 | env: 24 | GDSION_PATH: https://github.com/YuriSizov/gdsion/releases/download/${{ inputs.gdsion-version }}/libgdsion-${{ inputs.platform }}.zip 25 | run: | 26 | curl -JLO ${{ env.GDSION_PATH }} 27 | unzip libgdsion-${{ inputs.platform }}.zip 28 | 29 | # Web platform is exported via Linux, so install that version of GDSiON as well. 30 | - name: Install GDSiON Linux (Web) 31 | if: ${{ inputs.platform == 'web' }} 32 | shell: bash 33 | env: 34 | GDSION_PATH: https://github.com/YuriSizov/gdsion/releases/download/${{ inputs.gdsion-version }}/libgdsion-linux.zip 35 | run: | 36 | curl -JLO ${{ env.GDSION_PATH }} 37 | unzip -n libgdsion-linux.zip 38 | -------------------------------------------------------------------------------- /.github/actions/sign-godot-project/action.yml: -------------------------------------------------------------------------------- 1 | name: Codesign Godot Project 2 | description: Codesign and notarize Godot project export artifacts. 3 | 4 | inputs: 5 | platform: 6 | description: Target platform. 7 | required: true 8 | 9 | setup-env: 10 | description: Flag that enables the setup step. 11 | default: false 12 | codesign: 13 | description: Flag that enables the codesign step. 14 | default: false 15 | 16 | # Setup arguments. 17 | apple-cert-base64: 18 | required: true 19 | apple-cert-password: 20 | required: true 21 | 22 | # Codesign arguments. 23 | apple-dev-id: 24 | required: true 25 | apple-dev-app-id: 26 | required: true 27 | apple-dev-team-id: 28 | required: true 29 | apple-dev-password: 30 | required: true 31 | 32 | # Input/output arguments. 33 | directory: 34 | description: Path to the folder with the project. 35 | required: true 36 | target-name: 37 | description: Name of the project executable file or folder (like on macOS). 38 | required: true 39 | 40 | runs: 41 | using: composite 42 | steps: 43 | # macOS-specific steps. 44 | 45 | # Setup. 46 | 47 | - name: Set up the signing environment (macos) 48 | if: ${{ inputs.platform == 'macos' && inputs.setup-env == 'true' }} 49 | shell: bash 50 | env: 51 | APPLE_CERT_BASE64: ${{ inputs.apple-cert-base64 }} 52 | APPLE_CERT_PASSWORD: ${{ inputs.apple-cert-password }} 53 | run: $GITHUB_ACTION_PATH/macos/setup.sh 54 | 55 | # Codesign. 56 | 57 | - name: Sign and notarize the project (macos) 58 | if: ${{ inputs.platform == 'macos' && inputs.codesign == 'true' }} 59 | shell: bash 60 | env: 61 | APPLE_DEV_ID: ${{ inputs.apple-dev-id }} 62 | APPLE_DEV_APP_ID: ${{ inputs.apple-dev-app-id }} 63 | APPLE_DEV_TEAM_ID: ${{ inputs.apple-dev-team-id }} 64 | APPLE_DEV_PASSWORD: ${{ inputs.apple-dev-password }} 65 | APP_PATH: ${{ inputs.directory }}/${{ inputs.target-name }} 66 | run: $GITHUB_ACTION_PATH/macos/sign.sh 67 | -------------------------------------------------------------------------------- /.github/actions/sign-godot-project/macos/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Based on https://github.com/godot-jolt/godot-jolt/blob/master/scripts/ci_sign_macos.ps1 4 | 5 | certificate_base64="$APPLE_CERT_BASE64" 6 | certificate_password="$APPLE_CERT_PASSWORD" 7 | 8 | if [ -z "${certificate_base64}" ]; then 9 | echo "ERROR: Missing codesign certificate." 10 | exit 1 11 | fi 12 | if [ -z "${certificate_password}" ]; then 13 | echo "ERROR: Missing codesign certificate password." 14 | exit 1 15 | fi 16 | 17 | # Convert the certificate back to its file form. 18 | 19 | echo "Decoding the base64 certificate..." 20 | 21 | certificate_path="certificate.p12" 22 | base64 --decode -o ${certificate_path} <<< "${certificate_base64}" 23 | 24 | # Set up the keychain and import the certificate. 25 | 26 | keychain="ephemeral.keychain" 27 | keychain_password="$(openssl rand -base64 16)" 28 | 29 | echo "Creating the default keychain..." 30 | 31 | security create-keychain -p ${keychain_password} ${keychain} 32 | security default-keychain -s ${keychain} 33 | 34 | echo "Importing the certificate into the keychain..." 35 | 36 | security import ${certificate_path} -k ~/Library/Keychains/${keychain} -P ${certificate_password} -T /usr/bin/codesign 37 | security find-identity 38 | 39 | echo "Granting access to the keychain..." 40 | 41 | security set-key-partition-list -S "apple-tool:,apple:" -s -k ${keychain_password} ${keychain} 42 | security set-keychain-settings ${keychain} 43 | -------------------------------------------------------------------------------- /.github/actions/sign-godot-project/macos/sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Based on https://github.com/godot-jolt/godot-jolt/blob/master/scripts/ci_sign_macos.ps1 4 | 5 | apple_dev_id="$APPLE_DEV_ID" 6 | apple_dev_app_id="$APPLE_DEV_APP_ID" 7 | apple_dev_team_id="$APPLE_DEV_TEAM_ID" 8 | apple_dev_password="$APPLE_DEV_PASSWORD" 9 | 10 | app_path="$APP_PATH" 11 | archive_path="$APP_PATH.zip" 12 | 13 | if [ -z "${apple_dev_id}" ]; then 14 | echo "ERROR: Missing Apple developer ID." 15 | exit 1 16 | fi 17 | if [ -z "${apple_dev_app_id}" ]; then 18 | echo "ERROR: Missing Apple developer application ID." 19 | exit 1 20 | fi 21 | if [ -z "${apple_dev_team_id}" ]; then 22 | echo "ERROR: Missing Apple team ID." 23 | exit 1 24 | fi 25 | if [ -z "${apple_dev_password}" ]; then 26 | echo "ERROR: Missing Apple developer password." 27 | exit 1 28 | fi 29 | if [ -z "${app_path}" ]; then 30 | echo "ERROR: Missing application path to sign." 31 | exit 1 32 | fi 33 | 34 | # Sign, notarize, and staple the app. 35 | 36 | echo "Signing and verifying the app at '${app_path}'..." 37 | 38 | codesign --timestamp --verbose --deep --force --options runtime --sign "${apple_dev_app_id}" "${app_path}" 39 | codesign --verify "${app_path}" 40 | 41 | echo "Archiving and notarizing the signed app..." 42 | 43 | ditto -ck --keepParent "${app_path}" "${archive_path}" 44 | xcrun notarytool submit "${archive_path}" --apple-id ${apple_dev_id} --team-id ${apple_dev_team_id} --password ${apple_dev_password} --wait || { exit 1; } 45 | 46 | echo "Stapling the notarization ticket to the signed app..." 47 | 48 | xcrun stapler staple "${app_path}" 49 | 50 | echo "Cleaning up..." 51 | 52 | rm -f "${archive_path}" 53 | -------------------------------------------------------------------------------- /.github/actions/zip-folder/action.yml: -------------------------------------------------------------------------------- 1 | name: Zip up a folder 2 | description: Create a zip archive of a folder or folders. 3 | 4 | inputs: 5 | filename: 6 | description: Output file name for the archive. 7 | default: "" 8 | path: 9 | description: Base path for archive files. 10 | default: "." 11 | directory: 12 | description: Working directory where the command is called. 13 | default: "." 14 | split: 15 | description: Create a separate archive for each directory in the base path. Folder's name is the file name. 16 | default: false 17 | 18 | runs: 19 | using: composite 20 | steps: 21 | - name: 22 | shell: bash 23 | working-directory: ${{ inputs.directory }} 24 | env: 25 | ARCHIVE_OUTPUT_NAME: ${{ inputs.filename }} 26 | ARCHIVE_INCLUDE_PATH: ${{ inputs.path }} 27 | ARCHIVE_SPLIT: ${{ inputs.split == 'true' && 1 || 0 }} 28 | run: $GITHUB_ACTION_PATH/zip.sh 29 | -------------------------------------------------------------------------------- /.github/actions/zip-folder/zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Adapted from https://github.com/TheDoctor0/zip-release. 4 | # Create an archive or exit if the command fails. 5 | 6 | set -eu 7 | printf "\n📦 Creating zip archive...\n" 8 | 9 | if [ "$RUNNER_OS" = "Windows" ]; then 10 | if [ "$ARCHIVE_SPLIT" = 1 ]; then 11 | for name in $ARCHIVE_INCLUDE_PATH/*; do 12 | if [ -d "$name" ]; then 13 | include_path="$ARCHIVE_INCLUDE_PATH/$name/" 14 | 7z a -tzip "$name.zip" $include_path || { printf "\n⛔ Unable to create zip archive from %s.\n" "$include_path"; exit 1; } 15 | printf "\n✔ Successfully created %s archive.\n" "$name.zip" 16 | fi 17 | done 18 | else 19 | 7z a -tzip "$ARCHIVE_OUTPUT_NAME" $ARCHIVE_INCLUDE_PATH || { printf "\n⛔ Unable to create zip archive from %s.\n" "$ARCHIVE_INCLUDE_PATH"; exit 1; } 20 | printf "\n✔ Successfully created %s archive.\n" "$ARCHIVE_OUTPUT_NAME" 21 | fi 22 | else 23 | if [ "$ARCHIVE_SPLIT" = 1 ]; then 24 | for name in $ARCHIVE_INCLUDE_PATH/*; do 25 | if [ -d "$name" ]; then 26 | include_path="$ARCHIVE_INCLUDE_PATH/$name/" 27 | zip -r "$name.zip" $include_path || { printf "\n⛔ Unable to create zip archive from %s.\n" "$include_path"; exit 1; } 28 | printf "\n✔ Successfully created %s archive.\n" "$name.zip" 29 | fi 30 | done 31 | else 32 | zip -r "$ARCHIVE_OUTPUT_NAME" $ARCHIVE_INCLUDE_PATH || { printf "\n⛔ Unable to create zip archive from %s.\n" "$ARCHIVE_INCLUDE_PATH"; exit 1; } 33 | printf "\n✔ Successfully created %s archive.\n" "$ARCHIVE_OUTPUT_NAME" 34 | fi 35 | fi 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/build-documentation.yml: -------------------------------------------------------------------------------- 1 | name: Build Online Documentation 2 | 3 | on: 4 | workflow_call: 5 | 6 | # Make sure jobs cannot overlap. 7 | concurrency: 8 | group: docs-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build: 13 | name: Build online documentation 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install dependencies 20 | shell: bash 21 | working-directory: docs 22 | run: | 23 | python -m venv ./.venv 24 | source ./.venv/bin/activate 25 | pip install -r requirements.txt 26 | 27 | - name: Run the build script 28 | shell: bash 29 | working-directory: docs 30 | run: | 31 | source ./.venv/bin/activate 32 | python ./build.py --release 33 | 34 | # Upload the results. 35 | 36 | - name: Upload the project 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: boscaceoil-blue-docs 40 | path: "docs/out" 41 | retention-days: 14 42 | -------------------------------------------------------------------------------- /.github/workflows/build-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Pull Request 2 | 3 | on: 4 | pull_request: 5 | 6 | # Make sure jobs cannot overlap. 7 | concurrency: 8 | group: build-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | 13 | export-project: 14 | name: Export the project for target platforms 15 | uses: ./.github/workflows/export-project.yml 16 | 17 | build-docs: 18 | name: Build online documentation 19 | uses: ./.github/workflows/build-documentation.yml 20 | -------------------------------------------------------------------------------- /.github/workflows/build-release-tagged.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Tagged Release 2 | 3 | on: 4 | push: 5 | tags: 6 | # Match only tags that look like version numbers, e.g. 0.1, 2.3-beta, 4.5.6d, etc. 7 | - '[0-9]+.[0-9]+*' 8 | 9 | # Make sure jobs cannot overlap. 10 | concurrency: 11 | group: build-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | 16 | export-project: 17 | name: Export the project for target platforms 18 | uses: ./.github/workflows/export-project.yml 19 | secrets: inherit 20 | with: 21 | with-codesign: true 22 | 23 | publish-project: 24 | name: Package and publish the project 25 | needs: [ export-project ] 26 | uses: ./.github/workflows/publish-project.yml 27 | with: 28 | release-version: ${{ github.ref_name }} 29 | 30 | build-docs: 31 | name: Build online documentation 32 | uses: ./.github/workflows/build-documentation.yml 33 | -------------------------------------------------------------------------------- /.github/workflows/build-unstable.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Unstable (main branch) 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | # Make sure jobs cannot overlap. 9 | concurrency: 10 | group: build-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | 15 | export-project: 16 | name: Export the project for target platforms 17 | uses: ./.github/workflows/export-project.yml 18 | 19 | build-docs: 20 | name: Build online documentation 21 | uses: ./.github/workflows/build-documentation.yml 22 | -------------------------------------------------------------------------------- /.github/workflows/publish-project.yml: -------------------------------------------------------------------------------- 1 | name: Publish Project 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | release-version: 7 | required: true 8 | type: string 9 | 10 | # Make sure jobs cannot overlap. 11 | concurrency: 12 | group: publish-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | publish: 17 | name: Package and publish the project 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Download project artifacts 24 | uses: actions/download-artifact@v4 25 | with: 26 | path: export 27 | pattern: boscaceoil-blue-* 28 | 29 | - name: Untar downloaded artifacts 30 | shell: bash 31 | working-directory: export 32 | run: | 33 | for name in ./*; do 34 | if [ -d "$name" ]; then 35 | cd "./$name" 36 | if [ -f "boscaceoil-blue.tar" ]; then 37 | tar -xvf boscaceoil-blue.tar 38 | rm -f boscaceoil-blue.tar 39 | fi 40 | cd .. 41 | fi 42 | done 43 | 44 | - name: Archive project exports 45 | uses: ./.github/actions/zip-folder 46 | with: 47 | directory: export 48 | split: true 49 | 50 | - name: Make GitHub Release 51 | uses: ./.github/actions/make-release 52 | with: 53 | release-version: ${{ inputs.release-version }} 54 | 55 | - name: Update the release with the project 56 | shell: bash 57 | env: 58 | GH_TOKEN: ${{ github.token }} 59 | run: | 60 | gh release upload ${{ inputs.release-version }} export/*.zip --clobber 61 | 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project-specific ignores ### 2 | 3 | .references/ 4 | bin/ 5 | export/ 6 | 7 | ### Godot-specific ignores ### 8 | .godot/ 9 | *.tmp 10 | 11 | 12 | ### IDEs and code editors ### 13 | 14 | # Visual Studio Code 15 | .vscode/ 16 | *.code-workspace 17 | .history/ 18 | 19 | 20 | ### Operating systems ### 21 | 22 | # Linux 23 | *~ 24 | .directory 25 | 26 | # macOS 27 | .DS_Store 28 | __MACOSX 29 | 30 | # Windows 31 | # https://github.com/github/gitignore/blob/main/Global/Windows.gitignore 32 | [Tt]humbs.db 33 | [Tt]humbs.db:encryptable 34 | ehthumbs.db 35 | ehthumbs_vista.db 36 | *.stackdump 37 | [Dd]esktop.ini 38 | $RECYCLE.BIN/ 39 | *.cab 40 | *.msi 41 | *.msix 42 | *.msm 43 | *.msp 44 | *.lnk 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Yuri Sizov and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ---- 24 | 25 | This application is based on and reimplements code or parts of code from 26 | Bosca Ceoil by Terry Cavanagh, distributed under a BSD-2-Clause-Views license. 27 | 28 | ---- 29 | 30 | Bosca Ceoil License 31 | Source: https://github.com/TerryCavanagh/boscaceoil 32 | 33 | Copyright 1992-2013 Terry Cavanagh. All rights reserved. 34 | 35 | Redistribution and use in source and binary forms, with or without 36 | modification, are permitted provided that the following conditions are met: 37 | 38 | Redistributions of source code must retain the above copyright notice, this 39 | list of conditions and the following disclaimer. 40 | 41 | Redistributions in binary form must reproduce the above copyright notice, 42 | this list of conditions and the following disclaimer in the documentation 43 | and/or other materials provided with the distribution. 44 | 45 | THIS SOFTWARE IS PROVIDED BY TERRY CAVANAGH "AS IS" AND ANY EXPRESS OR IMPLIED 46 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 47 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 48 | EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 50 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 52 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 53 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 54 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | The views and conclusions contained in the software and documentation are 57 | those of the authors and should not be interpreted as representing official 58 | policies, either expressed or implied, of Terry Cavanagh. 59 | -------------------------------------------------------------------------------- /Main.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends MarginContainer 8 | 9 | var _default_window_title: String = "" 10 | 11 | @onready var _pattern_editor: Control = %PatternEditor 12 | @onready var _locked_indicator: Control = %LockedIndicator 13 | @onready var _highlight_manager: CanvasLayer = %HighlightManager 14 | 15 | 16 | func _enter_tree() -> void: 17 | Controller.window_manager.register_window() 18 | 19 | _default_window_title = get_window().title 20 | 21 | 22 | func _ready() -> void: 23 | Controller.window_manager.restore_window() 24 | 25 | _pattern_editor.visible = true 26 | _locked_indicator.visible = false 27 | Controller.io_manager.initialize_song() 28 | _edit_current_song() 29 | 30 | if not Engine.is_editor_hint(): 31 | if Controller.settings_manager.is_first_time(): 32 | Controller.show_welcome_message() 33 | 34 | Controller.song_loaded.connect(_edit_current_song) 35 | Controller.song_saved.connect(_update_window_title) 36 | 37 | Controller.controls_locked.connect(_show_locked_indicator) 38 | Controller.controls_unlocked.connect(_hide_locked_indicator) 39 | 40 | Controller.help_manager.highlight_requested.connect(_set_highlighted_node) 41 | Controller.help_manager.highlight_cleared.connect(_clear_highlighted_node) 42 | 43 | 44 | # Window decorations. 45 | 46 | func _edit_current_song() -> void: 47 | if Engine.is_editor_hint(): 48 | return 49 | 50 | _update_window_title() 51 | if Controller.current_song: 52 | Controller.current_song.song_changed.connect(_update_window_title) 53 | 54 | 55 | func _update_window_title() -> void: 56 | if Engine.is_editor_hint(): 57 | return 58 | 59 | if not Controller.current_song: 60 | get_window().title = _default_window_title 61 | return 62 | 63 | var song_name := "" if Controller.current_song.filename.is_empty() else Controller.current_song.filename.get_file() 64 | var song_dirty := "* " if Controller.current_song.is_dirty() else "" 65 | 66 | get_window().title = "%s%s - %s" % [ song_dirty, song_name, _default_window_title ] 67 | 68 | 69 | # Editor locking. 70 | 71 | func _show_locked_indicator(message: String) -> void: 72 | _pattern_editor.visible = false 73 | _locked_indicator.message = message 74 | _locked_indicator.visible = true 75 | 76 | 77 | func _hide_locked_indicator() -> void: 78 | _pattern_editor.visible = true 79 | _locked_indicator.visible = false 80 | 81 | 82 | # Node highlighting. 83 | 84 | func _set_highlighted_node(rect_getter: Callable) -> void: 85 | if rect_getter.is_valid(): 86 | _highlight_manager.highlight_rect_getter = rect_getter 87 | else: 88 | _highlight_manager.highlight_rect_getter = Callable() 89 | 90 | _highlight_manager.update_highlight() 91 | 92 | 93 | func _clear_highlighted_node() -> void: 94 | _highlight_manager.highlight_rect_getter = Callable() 95 | 96 | _highlight_manager.update_highlight() 97 | -------------------------------------------------------------------------------- /Main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=3 uid="uid://cf3nug1sfh1ix"] 2 | 3 | [ext_resource type="Script" path="res://Main.gd" id="1_63yj5"] 4 | [ext_resource type="Script" path="res://gui/widgets/FillerControl.gd" id="2_kg520"] 5 | [ext_resource type="PackedScene" uid="uid://l0bjoy6vwtbx" path="res://gui/Menu.tscn" id="2_ljcdm"] 6 | [ext_resource type="PackedScene" uid="uid://byfh0p3nqrosa" path="res://gui/views/PatternEditor.tscn" id="2_nrw1o"] 7 | [ext_resource type="PackedScene" uid="uid://lnyrbw2vvjwl" path="res://gui/views/LockedIndicator.tscn" id="3_f0rno"] 8 | [ext_resource type="Script" path="res://globals/PopupManager.gd" id="4_kqdnh"] 9 | [ext_resource type="PackedScene" uid="uid://dxtku0ujhowt5" path="res://gui/widgets/ToastMessage.tscn" id="5_carau"] 10 | [ext_resource type="Script" path="res://gui/widgets/HighlightManager.gd" id="6_gf5v8"] 11 | 12 | [node name="Main" type="MarginContainer"] 13 | anchors_preset = 15 14 | anchor_right = 1.0 15 | anchor_bottom = 1.0 16 | grow_horizontal = 2 17 | grow_vertical = 2 18 | script = ExtResource("1_63yj5") 19 | 20 | [node name="Main" type="VBoxContainer" parent="."] 21 | layout_mode = 2 22 | 23 | [node name="Filler" type="Control" parent="Main" node_paths=PackedStringArray("paired_node")] 24 | unique_name_in_owner = true 25 | custom_minimum_size = Vector2(0, 280) 26 | layout_mode = 2 27 | script = ExtResource("2_kg520") 28 | paired_node = NodePath("../../Menu") 29 | 30 | [node name="PatternEditor" parent="Main" instance=ExtResource("2_nrw1o")] 31 | unique_name_in_owner = true 32 | show_behind_parent = true 33 | layout_mode = 2 34 | 35 | [node name="LockedIndicator" parent="Main" instance=ExtResource("3_f0rno")] 36 | unique_name_in_owner = true 37 | visible = false 38 | layout_mode = 2 39 | size_flags_vertical = 3 40 | 41 | [node name="Menu" parent="." instance=ExtResource("2_ljcdm")] 42 | unique_name_in_owner = true 43 | custom_minimum_size = Vector2(0, 280) 44 | layout_mode = 2 45 | size_flags_vertical = 0 46 | 47 | [node name="ToastMessage" parent="." instance=ExtResource("5_carau")] 48 | layout_mode = 2 49 | size_flags_vertical = 8 50 | 51 | [node name="HighlightManager" type="CanvasLayer" parent="."] 52 | unique_name_in_owner = true 53 | script = ExtResource("6_gf5v8") 54 | 55 | [node name="HighlightIndicator" type="Control" parent="HighlightManager"] 56 | layout_mode = 3 57 | anchors_preset = 15 58 | anchor_right = 1.0 59 | anchor_bottom = 1.0 60 | grow_horizontal = 2 61 | grow_vertical = 2 62 | mouse_filter = 2 63 | 64 | [node name="PopupManager" type="CanvasLayer" parent="."] 65 | script = ExtResource("4_kqdnh") 66 | 67 | [node name="ClickCatcher" type="Control" parent="PopupManager"] 68 | layout_mode = 3 69 | anchors_preset = 15 70 | anchor_right = 1.0 71 | anchor_bottom = 1.0 72 | grow_horizontal = 2 73 | grow_vertical = 2 74 | 75 | [node name="SaveTimer" type="Timer" parent="."] 76 | unique_name_in_owner = true 77 | one_shot = true 78 | -------------------------------------------------------------------------------- /addons/bosca_exports/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Bosca Ceoil: Exports" 4 | description="A helper utility to customize exports" 5 | author="Yuri Sizov" 6 | version="1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/bosca_exports/plugin.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | extends EditorPlugin 9 | 10 | const BoscaWebExportPlugin := preload("res://addons/bosca_exports/BoscaWebExportPlugin.gd") 11 | 12 | var _export_plugin_web: EditorExportPlugin = null 13 | 14 | 15 | func _enter_tree() -> void: 16 | _export_plugin_web = BoscaWebExportPlugin.new() 17 | add_export_plugin(_export_plugin_web) 18 | 19 | 20 | func _exit_tree() -> void: 21 | if is_instance_valid(_export_plugin_web): 22 | remove_export_plugin(_export_plugin_web) 23 | -------------------------------------------------------------------------------- /assets/fonts/FFF Aquarius - License.txt: -------------------------------------------------------------------------------- 1 | The FFF Aquarius font is a part of a collection offered by FontsForFlash (http://www.fontsforflash.com/) — a now-defunct resource providing pixel fonts for Flash applications. 2 | The FFF resource is credited to Randy Caldwell. The author of the FFF Aquarius font itself is unknown. The license status of the font is unknown. 3 | 4 | The font is used by the original Bosca Ceoil application. Bosca Ceoil Blue reuses it in good faith. If you believe that this application violates the license of the FFF Aquarius font, please contact Yuri Sizov at yuris@humnom.net so the font can be replaced. 5 | -------------------------------------------------------------------------------- /assets/fonts/fff-aquarius-bold-condensed.msdf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/fonts/fff-aquarius-bold-condensed.msdf.ttf -------------------------------------------------------------------------------- /assets/fonts/fff-aquarius-bold-condensed.msdf.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://cjwuh3bvsqt57" 6 | path="res://.godot/imported/fff-aquarius-bold-condensed.msdf.ttf-b845f2a6e38365296f989bda25748736.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://assets/fonts/fff-aquarius-bold-condensed.msdf.ttf" 11 | dest_files=["res://.godot/imported/fff-aquarius-bold-condensed.msdf.ttf-b845f2a6e38365296f989bda25748736.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=true 20 | msdf_pixel_range=8 21 | msdf_size=36 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[{ 32 | "chars": [], 33 | "glyphs": [], 34 | "name": "New Configuration", 35 | "size": Vector2i(16, 0) 36 | }] 37 | language_support={} 38 | script_support={} 39 | opentype_features={} 40 | -------------------------------------------------------------------------------- /assets/fonts/fff-aquarius-bold-condensed.normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/fonts/fff-aquarius-bold-condensed.normal.ttf -------------------------------------------------------------------------------- /assets/fonts/fff-aquarius-bold-condensed.normal.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://blsoavvmevh5x" 6 | path="res://.godot/imported/fff-aquarius-bold-condensed.normal.ttf-142e0b9828cb72550410ad9b393271c5.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://assets/fonts/fff-aquarius-bold-condensed.normal.ttf" 11 | dest_files=["res://.godot/imported/fff-aquarius-bold-condensed.normal.ttf-142e0b9828cb72550410ad9b393271c5.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=4.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_down.png -------------------------------------------------------------------------------- /assets/icons/arrow_down.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dltrbt3gaisqw" 6 | path="res://.godot/imported/arrow_down.png-c93ce13fc34e8f54091b090433da4319.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_down.png" 14 | dest_files=["res://.godot/imported/arrow_down.png-c93ce13fc34e8f54091b090433da4319.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_down_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_down_small.png -------------------------------------------------------------------------------- /assets/icons/arrow_down_small.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://n8jw4gat8tg7" 6 | path="res://.godot/imported/arrow_down_small.png-cde559d99a71f41fc0bd63d1a2660bb0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_down_small.png" 14 | dest_files=["res://.godot/imported/arrow_down_small.png-cde559d99a71f41fc0bd63d1a2660bb0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_left.png -------------------------------------------------------------------------------- /assets/icons/arrow_left.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cypx2wsn8bvf2" 6 | path="res://.godot/imported/arrow_left.png-9db032e087fb76a48b03fa866d51c0b0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_left.png" 14 | dest_files=["res://.godot/imported/arrow_left.png-9db032e087fb76a48b03fa866d51c0b0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_left_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_left_small.png -------------------------------------------------------------------------------- /assets/icons/arrow_left_small.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dreol4dav8ocg" 6 | path="res://.godot/imported/arrow_left_small.png-4b01923d1ab60416f3bdd0b9fde0d365.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_left_small.png" 14 | dest_files=["res://.godot/imported/arrow_left_small.png-4b01923d1ab60416f3bdd0b9fde0d365.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_right.png -------------------------------------------------------------------------------- /assets/icons/arrow_right.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bl0lbiq37a2o2" 6 | path="res://.godot/imported/arrow_right.png-f44904af83932f488b0075e354ef2dc9.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_right.png" 14 | dest_files=["res://.godot/imported/arrow_right.png-f44904af83932f488b0075e354ef2dc9.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_right_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_right_small.png -------------------------------------------------------------------------------- /assets/icons/arrow_right_small.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bu4ylhahtvclx" 6 | path="res://.godot/imported/arrow_right_small.png-f2588bc9e2b048f27b76511dc56431fd.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_right_small.png" 14 | dest_files=["res://.godot/imported/arrow_right_small.png-f2588bc9e2b048f27b76511dc56431fd.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_up.png -------------------------------------------------------------------------------- /assets/icons/arrow_up.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bcnk14je7sagv" 6 | path="res://.godot/imported/arrow_up.png-9447ca0e8f31ae4bf4a5d58081e09faa.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_up.png" 14 | dest_files=["res://.godot/imported/arrow_up.png-9447ca0e8f31ae4bf4a5d58081e09faa.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_up_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_up_small.png -------------------------------------------------------------------------------- /assets/icons/arrow_up_small.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cbvbbfa1ojixi" 6 | path="res://.godot/imported/arrow_up_small.png-e0e1e6f532b22c5cc9e3af521d9849c0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_up_small.png" 14 | dest_files=["res://.godot/imported/arrow_up_small.png-e0e1e6f532b22c5cc9e3af521d9849c0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/arrow_updown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/arrow_updown.png -------------------------------------------------------------------------------- /assets/icons/arrow_updown.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dray0wdc6twqu" 6 | path="res://.godot/imported/arrow_updown.png-fc3f967d963bb81534906883ee8427f9.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/arrow_updown.png" 14 | dest_files=["res://.godot/imported/arrow_updown.png-fc3f967d963bb81534906883ee8427f9.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/cross.png -------------------------------------------------------------------------------- /assets/icons/cross.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dqatqvd3ie4cw" 6 | path="res://.godot/imported/cross.png-b6bc644c7d82ad130b8544bda2ea8519.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/cross.png" 14 | dest_files=["res://.godot/imported/cross.png-b6bc644c7d82ad130b8544bda2ea8519.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/fullscreen_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/fullscreen_off.png -------------------------------------------------------------------------------- /assets/icons/fullscreen_off.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ddcgvq3easmuh" 6 | path="res://.godot/imported/fullscreen_off.png-a4711a5c87d4a690743103ef9f5c2255.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/fullscreen_off.png" 14 | dest_files=["res://.godot/imported/fullscreen_off.png-a4711a5c87d4a690743103ef9f5c2255.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/fullscreen_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/fullscreen_on.png -------------------------------------------------------------------------------- /assets/icons/fullscreen_on.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b2uo4lffopiu7" 6 | path="res://.godot/imported/fullscreen_on.png-6eb667feb4e52afd23535e04240b6658.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/fullscreen_on.png" 14 | dest_files=["res://.godot/imported/fullscreen_on.png-6eb667feb4e52afd23535e04240b6658.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/minus.png -------------------------------------------------------------------------------- /assets/icons/minus.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://os3udjkncg7b" 6 | path="res://.godot/imported/minus.png-98e2584ab70b61c78f91932fbce84c1a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/minus.png" 14 | dest_files=["res://.godot/imported/minus.png-98e2584ab70b61c78f91932fbce84c1a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/pause.png -------------------------------------------------------------------------------- /assets/icons/pause.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://82f8rwsfmuq8" 6 | path="res://.godot/imported/pause.png-379e7f6da7dfcf892b36c0ac65c6e5bc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/pause.png" 14 | dest_files=["res://.godot/imported/pause.png-379e7f6da7dfcf892b36c0ac65c6e5bc.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/play.png -------------------------------------------------------------------------------- /assets/icons/play.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dxrpimbnhks71" 6 | path="res://.godot/imported/play.png-9f26a7e2bdee96a6e1c81c0a2e6e6aff.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/play.png" 14 | dest_files=["res://.godot/imported/play.png-9f26a7e2bdee96a6e1c81c0a2e6e6aff.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/plus.png -------------------------------------------------------------------------------- /assets/icons/plus.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://34gjffqqcubk" 6 | path="res://.godot/imported/plus.png-4e6d88a21847e36dab906427c2e3b51d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/plus.png" 14 | dest_files=["res://.godot/imported/plus.png-4e6d88a21847e36dab906427c2e3b51d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/random.png -------------------------------------------------------------------------------- /assets/icons/random.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://hfvrvhr83y46" 6 | path="res://.godot/imported/random.png-bb4bc61e5c4d1429cdd71ffc1d58a989.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/random.png" 14 | dest_files=["res://.godot/imported/random.png-bb4bc61e5c4d1429cdd71ffc1d58a989.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/record.png -------------------------------------------------------------------------------- /assets/icons/record.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://3r61aokrspvu" 6 | path="res://.godot/imported/record.png-71e74163cdb98c9f187323dfde7f20a0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/record.png" 14 | dest_files=["res://.godot/imported/record.png-71e74163cdb98c9f187323dfde7f20a0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/icons/stop.png -------------------------------------------------------------------------------- /assets/icons/stop.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://y5ab1acbg8ka" 6 | path="res://.godot/imported/stop.png-782855f1a6c6440fb208a1c8af723c44.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/icons/stop.png" 14 | dest_files=["res://.godot/imported/stop.png-782855f1a6c6440fb208a1c8af723c44.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_blue.png -------------------------------------------------------------------------------- /assets/logos/logo_blue.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bl2bn0gj2t4wo" 6 | path="res://.godot/imported/logo_blue.png-ee92435f35938a1ab4729eb0f2ef0e3c.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_blue.png" 14 | dest_files=["res://.godot/imported/logo_blue.png-ee92435f35938a1ab4729eb0f2ef0e3c.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_cyan.png -------------------------------------------------------------------------------- /assets/logos/logo_cyan.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://5xancynnbcua" 6 | path="res://.godot/imported/logo_cyan.png-e61cae3fc04e923b9b9d4b562a7f2819.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_cyan.png" 14 | dest_files=["res://.godot/imported/logo_cyan.png-e61cae3fc04e923b9b9d4b562a7f2819.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_gray.png -------------------------------------------------------------------------------- /assets/logos/logo_gray.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b6xkg7pqtvow1" 6 | path="res://.godot/imported/logo_gray.png-6fea98573883ade5f1bf8989473728f7.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_gray.png" 14 | dest_files=["res://.godot/imported/logo_gray.png-6fea98573883ade5f1bf8989473728f7.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_green.png -------------------------------------------------------------------------------- /assets/logos/logo_green.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://3rn5vd454rht" 6 | path="res://.godot/imported/logo_green.png-a84045d5a98188c1fb7c00a9c17e8615.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_green.png" 14 | dest_files=["res://.godot/imported/logo_green.png-a84045d5a98188c1fb7c00a9c17e8615.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_orange.png -------------------------------------------------------------------------------- /assets/logos/logo_orange.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b361brw1qnb1h" 6 | path="res://.godot/imported/logo_orange.png-ee18879f1b79ad729533f081dac607c5.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_orange.png" 14 | dest_files=["res://.godot/imported/logo_orange.png-ee18879f1b79ad729533f081dac607c5.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_purple.png -------------------------------------------------------------------------------- /assets/logos/logo_purple.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d4hrvp7dt5x07" 6 | path="res://.godot/imported/logo_purple.png-a0b1f47aea0cc5a33a2467c3bf3d9102.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_purple.png" 14 | dest_files=["res://.godot/imported/logo_purple.png-a0b1f47aea0cc5a33a2467c3bf3d9102.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_red.png -------------------------------------------------------------------------------- /assets/logos/logo_red.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b6u63egtmdflh" 6 | path="res://.godot/imported/logo_red.png-e9f10d1c8b53beb4cfbe34e6a27a96c0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_red.png" 14 | dest_files=["res://.godot/imported/logo_red.png-e9f10d1c8b53beb4cfbe34e6a27a96c0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/logos/logo_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/logos/logo_shadow.png -------------------------------------------------------------------------------- /assets/logos/logo_shadow.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://uxa5unpo0xp8" 6 | path="res://.godot/imported/logo_shadow.png-57aeb52a7ccd902eba2cd640595298bd.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/logos/logo_shadow.png" 14 | dest_files=["res://.godot/imported/logo_shadow.png-57aeb52a7ccd902eba2cd640595298bd.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/overlap-dither.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/assets/overlap-dither.png -------------------------------------------------------------------------------- /assets/overlap-dither.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d2aswndmtik56" 6 | path="res://.godot/imported/overlap-dither.png-eb6aba2379d0525df1d9bc72d022c3ff.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/overlap-dither.png" 14 | dest_files=["res://.godot/imported/overlap-dither.png-eb6aba2379d0525df1d9bc72d022c3ff.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=0 35 | -------------------------------------------------------------------------------- /default_bus_layout.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AudioBusLayout" format=3 uid="uid://uhbebwkia6y6"] 2 | 3 | [resource] 4 | -------------------------------------------------------------------------------- /dist/logo-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/dist/logo-full.png -------------------------------------------------------------------------------- /dist/logo-full.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://12wyc1w7cdfq" 6 | path="res://.godot/imported/logo-full.png-270015964da4b391c3f867b917dcc1b5.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://dist/logo-full.png" 14 | dest_files=["res://.godot/imported/logo-full.png-270015964da4b391c3f867b917dcc1b5.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /dist/web_assets/.gdignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dist/web_assets/boscaweb.patches.js: -------------------------------------------------------------------------------- 1 | /***************************************************/ 2 | /* Part of Bosca Ceoil Blue */ 3 | /* Copyright (c) 2025 Yuri Sizov and contributors */ 4 | /* Provided under MIT */ 5 | /***************************************************/ 6 | 7 | // Monkey-patch fetch to intercept Godot loading its bits and pieces. 8 | (function(window){ 9 | const _orig_fetch = window.fetch; 10 | window.fetch = async function(resource, options) { 11 | if (!(resource in BOSCA_FILE_SIZES)) { 12 | return await _orig_fetch(resource, options); 13 | } 14 | 15 | const response = await _orig_fetch(resource, options); 16 | const innerStream = new ReadableStream( 17 | { 18 | async start(controller) { 19 | const totalBytes = BOSCA_FILE_SIZES[resource]; 20 | let loadedBytes = 0; 21 | 22 | const reader = response.body.getReader(); 23 | 24 | while (true) { 25 | const { value, done } = await reader.read(); 26 | if (done) { 27 | bosca.setLoadingProgress(resource, totalBytes); 28 | break; 29 | } 30 | 31 | loadedBytes += value.byteLength 32 | bosca.setLoadingProgress(resource, loadedBytes); 33 | controller.enqueue(value); 34 | } 35 | 36 | reader.releaseLock(); 37 | controller.close(); 38 | } 39 | }, 40 | { 41 | status: response.status, 42 | statusText: response.statusText 43 | } 44 | ) 45 | 46 | const forwardedResponse = new Response(innerStream); 47 | for (const pair of response.headers.entries()) { 48 | forwardedResponse.headers.set(pair[0], pair[1]); 49 | } 50 | 51 | return forwardedResponse; 52 | } 53 | })(window); 54 | 55 | // Monkey-patch the Godot initializer to influence initialization where it cannot be configured. 56 | (function(window){ 57 | const _orig_Godot = window.Godot; 58 | 59 | window.Godot = function(Module) { 60 | // Use a pre-allocated buffer that uses a safer amount of maximum memory, which 61 | // avoids instant crashes in Safari. Although, there can still be memory issues 62 | // in Safari (both macOS and iOS/iPadOS), with some indication of improvements 63 | // starting with Safari 18. 64 | if (window.bosca.memory != null) { 65 | Module["wasmMemory"] = window.bosca.memory; 66 | } 67 | 68 | // The initializer can still throw exceptions, including an out of memory exception. 69 | // Due to nested levels of async and promise handling, this is not captured by 70 | // try-catching Engine.startGame(). But it can be captured here. 71 | try { 72 | return _orig_Godot(Module); 73 | } catch (err) { 74 | window.bosca._fatalError(err); 75 | } 76 | } 77 | })(window); 78 | -------------------------------------------------------------------------------- /dist/web_assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/dist/web_assets/logo.png -------------------------------------------------------------------------------- /dist/web_assets/og_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/dist/web_assets/og_image.png -------------------------------------------------------------------------------- /dist/windows-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/dist/windows-icon.ico -------------------------------------------------------------------------------- /docs/.gdignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Documentation-specific ignores. 2 | 3 | __pycache__ 4 | .venv 5 | out 6 | -------------------------------------------------------------------------------- /docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/assets/images/arrangements-dock-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/arrangements-dock-delete.png -------------------------------------------------------------------------------- /docs/assets/images/arrangements-dock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/arrangements-dock.png -------------------------------------------------------------------------------- /docs/assets/images/arrangements-drag-n-drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/arrangements-drag-n-drop.png -------------------------------------------------------------------------------- /docs/assets/images/arrangements-patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/arrangements-patterns.png -------------------------------------------------------------------------------- /docs/assets/images/arrangements-timeline-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/arrangements-timeline-select.png -------------------------------------------------------------------------------- /docs/assets/images/effects-swing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/effects-swing.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-categories.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-dock-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-dock-delete.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-pattern-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-pattern-picker.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-recording-values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-recording-values.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-recording.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-recording.png -------------------------------------------------------------------------------- /docs/assets/images/instruments-tuning-pads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/instruments-tuning-pads.png -------------------------------------------------------------------------------- /docs/assets/images/introduction-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/introduction-welcome.png -------------------------------------------------------------------------------- /docs/assets/images/io-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/io-export.png -------------------------------------------------------------------------------- /docs/assets/images/io-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/io-import.png -------------------------------------------------------------------------------- /docs/assets/images/overview-advanced-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-advanced-view.png -------------------------------------------------------------------------------- /docs/assets/images/overview-arrangement-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-arrangement-view.png -------------------------------------------------------------------------------- /docs/assets/images/overview-builtin-guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-builtin-guide.png -------------------------------------------------------------------------------- /docs/assets/images/overview-file-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-file-view.png -------------------------------------------------------------------------------- /docs/assets/images/overview-instrument-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-instrument-view.png -------------------------------------------------------------------------------- /docs/assets/images/overview-pattern-editor-drums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-pattern-editor-drums.png -------------------------------------------------------------------------------- /docs/assets/images/overview-pattern-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/overview-pattern-editor.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-drawing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-drawing.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-drumkits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-drumkits.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-instrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-instrument.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-length.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-length.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-placing-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-placing-notes.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-removing-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-removing-notes.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-scale-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-scale-key.png -------------------------------------------------------------------------------- /docs/assets/images/patterns-stamping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/images/patterns-stamping.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/assets/og_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/docs/assets/og_image.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | markdown == 3.7.0 2 | -------------------------------------------------------------------------------- /docs/src/community.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Community 3 | --- 4 | 5 | # Community and extra resources 6 | 7 | _Bosca Ceoil Blue_ is inherently a community-made project building off of the 10-year-long legacy of the original _Bosca Ceoil_, and you are welcome to be a part of it! 8 | 9 | Below you will find links to resources and communities related to _Bosca Ceoil_, where likeminded individuals can help you out and where you can share your work or share your tips. 10 | 11 | If you'd like to add a link of your own, please open a pull request against [this file](https://github.com/YuriSizov/boscaceoil-blue/blob/main/docs/src/community.md). Read more about contributing to the documentation [here](https://github.com/YuriSizov/boscaceoil-blue/blob/main/docs/README.md). 12 | 13 | 14 | ## Tutorials and overviews 15 | 16 | #### 🎥 [How to make Music for your Game - BOSCA CEOIL](https://www.youtube.com/watch?v=fZeZ75gM9p4) by **Brackeys** 17 | 18 | A gamedev educator and an overall legend _Brackeys_ made a tutorial for the original version of _Bosca Ceoil_, but its core principles stay true to this day. This is a great starting point in your Bosca journey! 19 | 20 | #### 🎥 [Bosca Ceoil Blue - Stupidly Simple Music Creation](https://www.youtube.com/watch?v=VBd_WdL8FFY) by **Gamefromscratch** 21 | 22 | In this video overview _Mike from Gamefromscratch_ covers the initial 3.0 release of _Bosca Ceoil Blue_ and gives you a brief tour of the app and its modern capabilities. 23 | 24 | 25 | ## User communities 26 | 27 | #### [r/BoscaCeoil](https://www.reddit.com/r/BoscaCeoil/) on **Reddit** 28 | 29 | A subreddit dedicated to sharing your music made with _Bosca Ceoil_, with a small but friendly community always ready to offer and advice. 30 | 31 | 32 | ## Official resources 33 | 34 | #### [GitHub Discussions](https://github.com/YuriSizov/boscaceoil-blue/discussions) 35 | 36 | A discussion platform provided by GitHub where you can ask questions about the app and share your ideas for improvements. 37 | 38 | #### [GitHub Issues](https://github.com/YuriSizov/boscaceoil-blue/issues) 39 | 40 | A bug tracker platform provided by GitHub where you can report issues and more formally propose enhancements and improvements. 41 | 42 | #### [Discord](https://discord.gg/S657Y9KPF9) 43 | 44 | A Discord server hosted by Yuri Sizov (lead project maintainer) where you can chat about _Bosca Ceoil Blue_, share your problems, and suggest new features in a more informal manner. 45 | 46 | -------------------------------------------------------------------------------- /docs/src/effects.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Effects 3 | --- 4 | 5 | # Effects and filters 6 | 7 | While individual instruments only allow you to configure a low-pass filter to adjust their sound, a variety of more interesting effects can be applied to the song overall. You can find these extra effects and filters in the `ADVANCED` view. 8 | 9 | ![](/images/overview-advanced-view.png) 10 | 11 | 12 | ## Global effects 13 | 14 | The following effects are available to you: 15 | 16 | - Delay 17 | - Chorus 18 | - Reverb 19 | - Distortion 20 | - Low boost 21 | - Compressor 22 | - High pass 23 | 24 | Only one of these effects can be applied at the same time. 25 | 26 | Typically, many of these effects allow for a very granular control via a number of settings. For the sake of simplicity, in _Bosca Ceoil_ you can enable and adjust the power of each effect and filter with just one slider. Setting the slider all the way to the left disables all global effects. 27 | 28 | 29 | ## Swing effect 30 | 31 | Separate from other effects is _swing_. Using _swing_ you can adjust the timing of the played notes. Normally, a note in _Bosca Ceoil Blue_ can be played at every tick of the metronome. The swing effect takes two consecutive notes in a pattern and adjusts their proportions, such that one of the notes takes the majority of their combined time and the other takes the minority. 32 | 33 | ![](/images/effects-swing.png) 34 | A rough demonstration of how note timings change with swing; in the app itself the notes' visuals remain unchanged when you adjust swing. 35 | 36 | In other words, if two notes take _two ticks_ to play, then adjusting the swing effect to the left makes the first note take the smaller portion of those two ticks, and the second note — to take the larger portion. Moving the slider to the right does the opposite, and the first note takes the larger portion. 37 | 38 | 39 | ## Exporting results 40 | 41 | Now that we've got every part of the creative process covered, it's time to render the song for the world to hear! Read on in [Export and Import](/export_import.html). 42 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | # Welcome to Bosca Ceoil Blue Docs! 6 | 7 | _[bús-ka kyó-al] — a music box._ 8 | 9 | **Bosca Ceoil: The Blue Album** (or _Bosca Ceoil Blue_, for short) is a simple and beginner-friendly app for making music. Using a step sequencer you can create anything from a simple beat to a complex melodic piece — with huge selection of synthesized instruments at your disposal! 10 | 11 | The best place to start learning about _Bosca Ceoil Blue_ and its features is the [Overview](/overview.html) page. 12 | 13 | ![](/images/introduction-welcome.png) 14 | 15 | 16 | ## Version compatibility 17 | 18 | _Bosca Ceoil Blue_ is available as a [download for all desktop platforms](https://yurisizov.itch.io/boscaceoil-blue) and [directly in your browser](https://humnom.net/apps/boscaceoil/beta/). The functionality of either version is identical, so this documentation covers both of them. If for whatever reason there is a notable difference in behavior between the desktop and the browser version, it will be stated explicitly. 19 | 20 | The hosted version of this documentation covers functionality of the most recent stable release. Older versions can be viewed via GitHub (e.g., [version 3.1](https://github.com/YuriSizov/boscaceoil-blue/tree/3.1-stable/docs)), including pre-release versions. 21 | 22 | Versions prior to 3.0 (i.e. original _Bosca Ceoil_) are not covered by this documentation, however general concepts of _Bosca Ceoil_ remain unchanged and you can loosely apply the information found here to any version. 23 | 24 | 25 | ## Available languages 26 | 27 | _Bosca Ceoil Blue_ is available in **English**, and so is this documentation. If you have made a tutorial or an overview of the app in your native language, please feel free to add a link to it to the [Community](/community.html) page. 28 | 29 | 30 | ## Contributing 31 | 32 | If you would like to contribute to this documentation, you can learn about the process [here](https://github.com/YuriSizov/boscaceoil-blue/blob/main/docs/README.md). 33 | 34 | 35 | ## License 36 | 37 | _Bosca Ceoil Blue_ is provided under an [MIT license](https://github.com/YuriSizov/boscaceoil-blue/blob/main/LICENSE), and so is this documentation. Contributors to this documentation are considered contributors to the project itself and are credited accordingly. 38 | 39 | 40 | ## Your Support 41 | 42 | _Bosca Ceoil Blue_ is an open source and free project. We'd like to make sure that the project receives necessary attention for as long as possible. If you're comfortable with it and want to help, please consider supporting the project financially: 43 | 44 | - The best way to do this is to become a supporter via [Patreon](https://www.patreon.com/YuriSizov). 45 | - You can also leave a one-time tip with your download on [itch.io](https://yurisizov.itch.io/boscaceoil-blue). 46 | 47 | Every dollar helps, so please consider donating even if it's a little! Thank you very much <3 48 | -------------------------------------------------------------------------------- /docs/templates/article.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %PAGE_TITLE% 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 30 |
31 | %PAGE_CONTENT% 32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/tools/colorize.py: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | # Helper script to abstract colorizing console output in builder. 8 | 9 | # ANSI escape codes. 10 | 11 | RESET = '\033[0m' 12 | BOLD = '\033[1m' 13 | FAINT = '\033[2m' 14 | ITALIC = '\033[3m' 15 | UNDERLINE = '\033[4m' 16 | 17 | BLACK = '\033[30m' 18 | RED = '\033[31m' 19 | GREEN = '\033[32m' 20 | YELLOW = '\033[33m' 21 | BLUE = '\033[34m' 22 | MAGENTA = '\033[35m' 23 | CYAN = '\033[36m' 24 | WHITE = '\033[37m' 25 | 26 | BLACK_BG = '\033[40m' 27 | RED_BG = '\033[41m' 28 | GREEN_BG = '\033[42m' 29 | YELLOW_BG = '\033[43m' 30 | BLUE_BG = '\033[44m' 31 | MAGENTA_BG = '\033[45m' 32 | CYAN_BG = '\033[46m' 33 | WHITE_BG = '\033[47m' 34 | 35 | BRIGHT_BLACK = '\033[90m' 36 | BRIGHT_RED = '\033[91m' 37 | BRIGHT_GREEN = '\033[92m' 38 | BRIGHT_YELLOW = '\033[93m' 39 | BRIGHT_BLUE = '\033[94m' 40 | BRIGHT_MAGENTA = '\033[95m' 41 | BRIGHT_CYAN = '\033[96m' 42 | BRIGHT_WHITE = '\033[97m' 43 | 44 | BRIGHT_BLACK_BG = '\033[100m' 45 | BRIGHT_RED_BG = '\033[101m' 46 | BRIGHT_GREEN_BG = '\033[102m' 47 | BRIGHT_YELLOW_BG = '\033[103m' 48 | BRIGHT_BLUE_BG = '\033[104m' 49 | BRIGHT_MAGENTA_BG = '\033[105m' 50 | BRIGHT_CYAN_BG = '\033[106m' 51 | BRIGHT_WHITE_BG = '\033[107m' 52 | 53 | 54 | # Helper methods. 55 | 56 | def bold(text): 57 | return f"{BOLD}{text}{RESET}" 58 | def faint(text): 59 | return f"{FAINT}{text}{RESET}" 60 | def italic(text): 61 | return f"{ITALIC}{text}{RESET}" 62 | def underline(text): 63 | return f"{UNDERLINE}{text}{RESET}" 64 | 65 | def gray(text): 66 | return f"{BRIGHT_BLACK}{text}{RESET}" 67 | def red(text): 68 | return f"{BRIGHT_RED}{text}{RESET}" 69 | def green(text): 70 | return f"{BRIGHT_GREEN}{text}{RESET}" 71 | def yellow(text): 72 | return f"{BRIGHT_YELLOW}{text}{RESET}" 73 | def blue(text): 74 | return f"{BRIGHT_BLUE}{text}{RESET}" 75 | def magenta(text): 76 | return f"{BRIGHT_MAGENTA}{text}{RESET}" 77 | def cyan(text): 78 | return f"{BRIGHT_CYAN}{text}{RESET}" 79 | def white(text): 80 | return f"{BRIGHT_WHITE}{text}{RESET}" 81 | -------------------------------------------------------------------------------- /docs/tools/paths.py: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | from markdown import Extension 8 | from markdown.treeprocessors import Treeprocessor 9 | import xml.etree.ElementTree as etree 10 | 11 | 12 | # Convert link and image paths that start with "/" to absolute paths using configured base. 13 | class AbsoluteLinkProcessor(Treeprocessor): 14 | def __init__(self, md, config): 15 | super().__init__(md) 16 | 17 | self.base_path = config["base"] 18 | 19 | 20 | def run(self, root: etree.Element): 21 | self.update_links(root) 22 | self.update_images(root) 23 | 24 | 25 | def update_links(self, root: etree.Element): 26 | for el in root.iter("a"): 27 | if "href" in el.attrib and el.attrib["href"].startswith("/"): 28 | path = self.base_path + el.attrib["href"] 29 | # Make sure the content is immediately visible on mobile, but only 30 | # if there are no other hashes in the URL. 31 | if path.endswith(".html"): 32 | path += "#_content" 33 | 34 | el.attrib["href"] = path 35 | 36 | 37 | def update_images(self, root: etree.Element): 38 | for el in root.iter("img"): 39 | if "src" in el.attrib and el.attrib["src"].startswith("/"): 40 | el.set("src", self.base_path + el.attrib["src"]) 41 | 42 | 43 | class MarkdownPaths(Extension): 44 | def __init__(self, **kwargs): 45 | self.config = { 46 | 'base': [ 47 | '', 'Base path for absolute links.' 48 | ], 49 | } 50 | 51 | super().__init__(**kwargs) 52 | 53 | 54 | def extendMarkdown(self, md): 55 | md.treeprocessors.register(AbsoluteLinkProcessor(md, self.getConfigs()), 'abslinks', 1) 56 | -------------------------------------------------------------------------------- /editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{css}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /enums/ColorPalette.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name ColorPalette extends Object 8 | 9 | const PALETTE_BLUE := 0 10 | const PALETTE_PURPLE := 1 11 | const PALETTE_RED := 2 12 | const PALETTE_ORANGE := 3 13 | const PALETTE_GREEN := 4 14 | const PALETTE_CYAN := 5 15 | const MAX := 6 16 | 17 | ## Fallback value, but can also be set directly. 18 | const PALETTE_GRAY := 20 19 | 20 | 21 | static func validate(value: int) -> int: 22 | if value == PALETTE_GRAY: 23 | return value 24 | 25 | return ValueValidator.index(value, ColorPalette.MAX, "Invalid value: Expected an index value between 0 and %d, or %d, got %d instead." % [ ColorPalette.MAX - 1, ColorPalette.PALETTE_GRAY, value ]) 26 | -------------------------------------------------------------------------------- /enums/Effect.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name Effect extends Object 8 | 9 | const EFFECT_DELAY := 0 10 | const EFFECT_CHORUS := 1 11 | const EFFECT_REVERB := 2 12 | const EFFECT_DISTORTION := 3 13 | const EFFECT_LOW_BOOST := 4 14 | const EFFECT_COMPRESSOR := 5 15 | const EFFECT_HIGH_PASS := 6 16 | const MAX := 7 17 | 18 | const _effect_name_map := { 19 | EFFECT_DELAY: "DELAY", 20 | EFFECT_CHORUS: "CHORUS", 21 | EFFECT_REVERB: "REVERB", 22 | EFFECT_DISTORTION: "DISTORTION", 23 | EFFECT_LOW_BOOST: "LOW BOOST", 24 | EFFECT_COMPRESSOR: "COMPRESSOR", 25 | EFFECT_HIGH_PASS: "HIGH PASS", 26 | } 27 | 28 | 29 | static func get_effect_name(effect: int) -> String: 30 | return _effect_name_map[effect] 31 | -------------------------------------------------------------------------------- /enums/Note.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name Note extends Object 8 | 9 | const NOTE_C := 0 10 | const NOTE_CS := 1 11 | const NOTE_D := 2 12 | const NOTE_DS := 3 13 | const NOTE_E := 4 14 | const NOTE_F := 5 15 | const NOTE_FS := 6 16 | const NOTE_G := 7 17 | const NOTE_GS := 8 18 | const NOTE_A := 9 19 | const NOTE_AS := 10 20 | const NOTE_B := 11 21 | const MAX := 12 22 | 23 | # We don't expect the note enum to change, so we just assume the same order here, for compactness. 24 | const _note_name_map_cdefgab := [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ] 25 | const _note_name_map_doremi := [ "Do", "Do#", "Re", "Re#", "Mi", "Fa", "Fa#", "Sol", "Sol#", "La", "La#", "Si" ] 26 | const _note_name_map_mml := [ "c", "c+", "d", "d+", "e", "f", "f+", "g", "g+", "a", "a+", "b" ] 27 | 28 | 29 | static func get_note_name(note: int) -> String: 30 | var normalized := note % MAX 31 | 32 | var note_format := -1 33 | if not Engine.is_editor_hint(): 34 | note_format = Controller.settings_manager.get_note_format() 35 | 36 | match note_format: 37 | SettingsManager.NoteFormat.FORMAT_CDEFGAB: 38 | return _note_name_map_cdefgab[normalized] 39 | SettingsManager.NoteFormat.FORMAT_DOREMI: 40 | return _note_name_map_doremi[normalized] 41 | 42 | return _note_name_map_cdefgab[normalized] 43 | 44 | 45 | static func get_note_mml(note: int) -> String: 46 | var normalized := note % MAX 47 | return _note_name_map_mml[normalized] 48 | 49 | 50 | static func get_note_octave(note: int) -> int: 51 | @warning_ignore("integer_division") 52 | return note / MAX # SiON octave numbers are 0-based. 53 | 54 | 55 | static func is_note_sharp(note: int) -> bool: 56 | var normalized := note % MAX 57 | return normalized in [ 1, 3, 6, 8, 10 ] 58 | -------------------------------------------------------------------------------- /globals/DebugManager.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | ## Component responsible for various runtime debug tools. 8 | class_name DebugManager extends RefCounted 9 | 10 | 11 | func activate_debug(key: int) -> void: 12 | match key: 13 | 0: 14 | _debug_song_merger() 15 | 16 | # Helpers. 17 | 18 | func _debug_song_merger() -> void: 19 | if Engine.is_editor_hint() || not Controller.current_song: 20 | return 21 | 22 | print("DebugManager: SongMerger debug activated.") 23 | var current_song := Controller.current_song 24 | 25 | print("") 26 | print("DebugManager: Encoding patterns with SongMerger.") 27 | var encoded_patterns: Array[SongMerger.EncodedPattern] = [] 28 | 29 | var p := 0 30 | for pattern in current_song.patterns: 31 | print("# PATTERN %d" % [ p ]) 32 | var instrument := current_song.instruments[pattern.instrument_idx] 33 | var enc_pattern := SongMerger.encode_pattern(pattern, instrument, current_song.pattern_size) 34 | encoded_patterns.push_back(enc_pattern) 35 | 36 | var t := 0 37 | for track in enc_pattern.tracks: 38 | print("## TRACK %d" % [ t ]) 39 | prints(track.get_track_mask_string()) 40 | 41 | print(" Track notes:") 42 | for note in track.get_notes(): 43 | if note: 44 | prints(note.get_mask_string()) 45 | 46 | t += 1 47 | 48 | p += 1 49 | 50 | var bytes_per_pattern := floori(current_song.pattern_size / 8.0) 51 | var pattern_inset := current_song.pattern_size + bytes_per_pattern 52 | var arrangement_bars := current_song.arrangement.timeline_bars.slice(0, current_song.arrangement.timeline_length) 53 | 54 | print("") 55 | print("DebugManager: Packing song per channel with SongMerger.") 56 | for i in Arrangement.CHANNEL_NUMBER: 57 | print("# CHANNEL %d" % [ i ]) 58 | var channel_sequences := SongMerger.encode_arrangement_channel(arrangement_bars, i, encoded_patterns, current_song.pattern_size) 59 | 60 | var cs := 0 61 | for sequence in channel_sequences: 62 | print("## SEQUENCE %d" % [ cs ]) 63 | prints(" Sequence time:", sequence._time, "(%d residue)" % [ sequence._last_residue ]) 64 | print(" Sequence tracks:") 65 | 66 | var t := 0 67 | for track in sequence.get_tracks(): 68 | print(" ".repeat(t * pattern_inset), track.get_track_mask_string()) 69 | t += track.get_track_time(current_song.pattern_size) 70 | 71 | cs += 1 72 | 73 | print("") 74 | print("DebugManager: Packing full song with SongMerger.") 75 | var arrangement_sequences := SongMerger.encode_arrangement(arrangement_bars, encoded_patterns, current_song.pattern_size) 76 | 77 | var ars := 0 78 | for sequence in arrangement_sequences: 79 | print("# SEQUENCE %d" % [ ars ]) 80 | prints(" Sequence time:", sequence._time, "(%d residue)" % [ sequence._last_residue ]) 81 | print(" Sequence tracks:") 82 | 83 | var t := 0 84 | for track in sequence.get_tracks(): 85 | print(" ".repeat(t * pattern_inset), track.get_track_mask_string()) 86 | t += track.get_track_time(current_song.pattern_size) 87 | 88 | ars += 1 89 | -------------------------------------------------------------------------------- /gui/MainWindow.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends Window 8 | 9 | 10 | # HACK: This is a naive fix to an engine bug. For some reason, window's content scale factor 11 | # affects controls' combined required minimum size, making it smaller the larger the scale is. 12 | # This doesn't seem rational or logical, and the difference isn't even proportional to scale. 13 | # 14 | # Experimentally, I identified that global transform matrices of child controls help to 15 | # counter-act the issue. So here we are. 16 | func _get_contents_minimum_size() -> Vector2: 17 | var content_min_size := Vector2.ZERO 18 | 19 | for child in get_children(): 20 | if child is not Control: 21 | continue 22 | 23 | var child_control := child as Control 24 | var child_pos := child_control.position 25 | var child_min_size := (child_control.get_combined_minimum_size() * child_control.get_global_transform()).floor() 26 | content_min_size = content_min_size.max(child_pos + child_min_size) 27 | 28 | # Adjusting by the scale factor allows us to correctly shrink the window for scales < 1.0. 29 | # Previous logic was only tested with scales >= 1.0, which worked by happenstance. 30 | return content_min_size * content_scale_factor 31 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_blue.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://dbtg5fe66mhr2"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd2"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.168627, 0.129412, 0.592157, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtlde"] 13 | color = Color(0.168627, 0.129412, 0.592157, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp82"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.231373, 0.152941, 0.933333, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm52"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.170246, 0.408523, 1, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.168627, 0.129412, 0.592157, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.231373, 0.152941, 0.933333, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd2") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtlde") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp82") 39 | NoteMap/colors/border_color = Color(0.0352941, 0.0509804, 0.235294, 1) 40 | NoteMap/colors/border_dark_color = Color(0.0196078, 0.027451, 0.121569, 1) 41 | NoteMap/colors/note_color = Color(0.231373, 0.152941, 0.933333, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.168627, 0.129412, 0.592157, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.737255, 0.811765, 1, 1) 44 | PadSlider/colors/cursor_color = Color(0.231373, 0.152941, 0.933333, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.168627, 0.129412, 0.592157, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm52") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_cyan.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://c1ym8arf5veh7"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd7"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.0392157, 0.337255, 0.541176, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtldf"] 13 | color = Color(0.0392157, 0.337255, 0.541176, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp87"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.0745098, 0.564706, 0.909804, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm57"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0, 0.626403, 0.875412, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.0392157, 0.337255, 0.541176, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.0745098, 0.564706, 0.909804, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd7") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtldf") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp87") 39 | NoteMap/colors/border_color = Color(0.0352941, 0.113725, 0.227451, 1) 40 | NoteMap/colors/border_dark_color = Color(0.0196078, 0.054902, 0.117647, 1) 41 | NoteMap/colors/note_color = Color(0.0745098, 0.564706, 0.909804, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.0392157, 0.337255, 0.541176, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.729412, 0.890196, 0.980392, 1) 44 | PadSlider/colors/cursor_color = Color(0.0745098, 0.564706, 0.909804, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.0392157, 0.337255, 0.541176, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm57") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_gray.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://dkb62tku7dpgs"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd5"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.376471, 0.376471, 0.376471, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtldc"] 13 | color = Color(0.376471, 0.376471, 0.376471, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp8c"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.54902, 0.54902, 0.54902, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm54"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.635391, 0.635391, 0.635391, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.376471, 0.376471, 0.376471, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.54902, 0.54902, 0.54902, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd5") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtldc") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp8c") 39 | NoteMap/colors/border_color = Color(0.121569, 0.121569, 0.121569, 1) 40 | NoteMap/colors/border_dark_color = Color(0.054902, 0.054902, 0.054902, 1) 41 | NoteMap/colors/note_color = Color(0.54902, 0.54902, 0.54902, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.376471, 0.376471, 0.376471, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.890196, 0.890196, 0.890196, 1) 44 | PadSlider/colors/cursor_color = Color(0.54902, 0.54902, 0.54902, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.376471, 0.376471, 0.376471, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm54") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_green.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://c4sm8xgjinucy"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd0"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.12549, 0.498039, 0.0784314, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtldd"] 13 | color = Color(0.12549, 0.498039, 0.0784314, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp86"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.211765, 0.843137, 0.141176, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm56"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.251047, 0.758026, 0, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.12549, 0.498039, 0.0784314, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.211765, 0.843137, 0.141176, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd0") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtldd") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp86") 39 | NoteMap/colors/border_color = Color(0.0352941, 0.231373, 0.0862745, 1) 40 | NoteMap/colors/border_dark_color = Color(0.0196078, 0.117647, 0.0470588, 1) 41 | NoteMap/colors/note_color = Color(0.211765, 0.843137, 0.141176, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.12549, 0.498039, 0.0784314, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.784314, 0.964706, 0.74902, 1) 44 | PadSlider/colors/cursor_color = Color(0.211765, 0.843137, 0.141176, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.12549, 0.498039, 0.0784314, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm56") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_orange.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://0u14f3q6dymc"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd4"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.486275, 0.352941, 0.0901961, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtlda"] 13 | color = Color(0.486275, 0.352941, 0.0901961, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp84"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.823529, 0.6, 0.160784, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm50"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.941688, 0.627425, 0, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.486275, 0.352941, 0.0901961, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.823529, 0.6, 0.160784, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd4") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtlda") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp84") 39 | NoteMap/colors/border_color = Color(0.231373, 0.203922, 0.0352941, 1) 40 | NoteMap/colors/border_dark_color = Color(0.117647, 0.105882, 0.0196078, 1) 41 | NoteMap/colors/note_color = Color(0.823529, 0.6, 0.160784, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.486275, 0.352941, 0.0901961, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.956863, 0.878431, 0.756863, 1) 44 | PadSlider/colors/cursor_color = Color(0.823529, 0.6, 0.160784, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.486275, 0.352941, 0.0901961, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm50") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_purple.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://djtns5uw7yxwh"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd3"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.356863, 0.0196078, 0.560784, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtldz"] 13 | color = Color(0.356863, 0.0196078, 0.560784, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp83"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.611765, 0.0431373, 0.941176, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm53"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.726172, 0.213341, 1, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.356863, 0.0196078, 0.560784, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.611765, 0.0431373, 0.941176, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd3") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtldz") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp83") 39 | NoteMap/colors/border_color = Color(0.124637, 0.0025765, 0.213626, 1) 40 | NoteMap/colors/border_dark_color = Color(0.0557228, 0.000808302, 0.110571, 1) 41 | NoteMap/colors/note_color = Color(0.611765, 0.0431373, 0.941176, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.356863, 0.0196078, 0.560784, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.878431, 0.72549, 1, 1) 44 | PadSlider/colors/cursor_color = Color(0.611765, 0.0431373, 0.941176, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.356863, 0.0196078, 0.560784, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm53") 47 | -------------------------------------------------------------------------------- /gui/theme/instruments/instrument_theme_red.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=6 format=3 uid="uid://56y8ylwbl15b"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://di3xykrb8awus" path="res://gui/theme/pad_slider_pattern.tres" id="1_16fbj"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iavd1"] 6 | content_margin_left = 16.0 7 | content_margin_top = 8.0 8 | content_margin_right = 16.0 9 | content_margin_bottom = 8.0 10 | bg_color = Color(0.486275, 0.0901961, 0.137255, 1) 11 | 12 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_xtldh"] 13 | color = Color(0.486275, 0.0901961, 0.137255, 1) 14 | thickness = 3 15 | vertical = true 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujp81"] 18 | content_margin_left = 12.0 19 | content_margin_top = 3.0 20 | content_margin_right = 12.0 21 | content_margin_bottom = 3.0 22 | bg_color = Color(0.823529, 0.160784, 0.227451, 1) 23 | 24 | [sub_resource type="StyleBoxTexture" id="StyleBoxTexture_2vm51"] 25 | texture = ExtResource("1_16fbj") 26 | axis_stretch_horizontal = 1 27 | axis_stretch_vertical = 1 28 | modulate_color = Color(0.978498, 4.04298e-06, 1.15514e-06, 1) 29 | 30 | [resource] 31 | InstrumentDock/colors/item_color = Color(0.486275, 0.0901961, 0.137255, 1) 32 | InstrumentDock/colors/item_gutter_color = Color(0.823529, 0.160784, 0.227451, 1) 33 | InstrumentSettingsPanel/base_type = &"PanelContainer" 34 | InstrumentSettingsPanel/styles/panel = SubResource("StyleBoxFlat_iavd1") 35 | InstrumentVSeparator/base_type = &"VSeparator" 36 | InstrumentVSeparator/styles/separator = SubResource("StyleBoxLine_xtldh") 37 | InstrumentWidgetPanel/base_type = &"PanelContainer" 38 | InstrumentWidgetPanel/styles/panel = SubResource("StyleBoxFlat_ujp81") 39 | NoteMap/colors/border_color = Color(0.227451, 0.054902, 0.0352941, 1) 40 | NoteMap/colors/border_dark_color = Color(0.121569, 0.0313726, 0.0196078, 1) 41 | NoteMap/colors/note_color = Color(0.823529, 0.160784, 0.227451, 1) 42 | NoteMap/colors/note_sharp_color = Color(0.486275, 0.0901961, 0.137255, 1) 43 | NoteMap/colors/octave_bar_color = Color(0.956863, 0.756863, 0.788235, 1) 44 | PadSlider/colors/cursor_color = Color(0.823529, 0.160784, 0.227451, 1) 45 | PadSlider/colors/cursor_outline_color = Color(0.486275, 0.0901961, 0.137255, 1) 46 | PadSlider/styles/background_pattern = SubResource("StyleBoxTexture_2vm51") 47 | -------------------------------------------------------------------------------- /gui/theme/navigation_buttons.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ButtonGroup" format=3 uid="uid://duqg28jfenc12"] 2 | 3 | [resource] 4 | resource_local_to_scene = false 5 | resource_name = "NavigationButtons" 6 | -------------------------------------------------------------------------------- /gui/theme/pad_slider_pattern.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://di3xykrb8awus"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_shdrt"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.5) 6 | colors = PackedColorArray(0.111197, 0.111197, 0.111197, 1, 0.157753, 0.157753, 0.157753, 1) 7 | metadata/_snap_enabled = true 8 | metadata/_snap_count = 2 9 | 10 | [resource] 11 | gradient = SubResource("Gradient_shdrt") 12 | width = 2 13 | height = 8 14 | fill_to = Vector2(0, 1) 15 | metadata/_snap_enabled = true 16 | metadata/_snap_count = 2 17 | -------------------------------------------------------------------------------- /gui/views/ArrangementView.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends MarginContainer 8 | 9 | 10 | func _ready() -> void: 11 | if not Engine.is_editor_hint(): 12 | Controller.help_manager.reference_node(HelpManager.StepNodeRef.ARRANGEMENT_EDITOR_VIEW, get_global_rect) 13 | -------------------------------------------------------------------------------- /gui/views/ArrangementView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://c1oriajgloul5"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/ArrangementView.gd" id="1_j5oys"] 4 | [ext_resource type="PackedScene" uid="uid://cee0lffxnyap0" path="res://gui/views/pattern_map/PatternMap.tscn" id="2_m5ovh"] 5 | [ext_resource type="PackedScene" uid="uid://bbbxnaw4cute2" path="res://gui/widgets/ItemDock.tscn" id="3_16dfn"] 6 | [ext_resource type="Script" path="res://gui/views/pattern_map/PatternDock.gd" id="4_pbhaf"] 7 | 8 | [node name="ArrangementView" type="MarginContainer"] 9 | auto_translate_mode = 1 10 | anchors_preset = 15 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | grow_horizontal = 2 14 | grow_vertical = 2 15 | script = ExtResource("1_j5oys") 16 | 17 | [node name="Layout" type="HBoxContainer" parent="."] 18 | layout_mode = 2 19 | 20 | [node name="PatternMap" parent="Layout" instance=ExtResource("2_m5ovh")] 21 | unique_name_in_owner = true 22 | layout_mode = 2 23 | size_flags_horizontal = 3 24 | 25 | [node name="PatternDock" parent="Layout" instance=ExtResource("3_16dfn")] 26 | unique_name_in_owner = true 27 | custom_minimum_size = Vector2(200, 0) 28 | layout_mode = 2 29 | script = ExtResource("4_pbhaf") 30 | add_button_text = "ADD NEW" 31 | drop_alignment = 2 32 | -------------------------------------------------------------------------------- /gui/views/GeneralHelpView.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends MarginContainer 8 | 9 | const SHORCUTS_POPUP_SCENE := preload("res://gui/widgets/popups/ShortcutHelpPopup.tscn") 10 | 11 | var _shortcut_help: WindowPopup = null 12 | 13 | @onready var _navigate_back_button: BackButton = %NavigateBack 14 | @onready var _basic_guide_button: SquishyButton = %StartBasicGuide 15 | @onready var _advanced_guide_button: SquishyButton = %StartAdvancedGuide 16 | @onready var _show_shortcuts_button: SquishyButton = %ShowShortcutsButton 17 | @onready var _short_shortcut_list: VBoxContainer = %ShortShortcutList 18 | 19 | 20 | func _init() -> void: 21 | _shortcut_help = SHORCUTS_POPUP_SCENE.instantiate() 22 | 23 | 24 | func _ready() -> void: 25 | _shortcut_help.add_button("Close", _shortcut_help.close_popup) 26 | 27 | _navigate_back_button.pressed.connect(Controller.navigate_to.bind(Menu.NavigationTarget.FILE)) 28 | _basic_guide_button.pressed.connect(Controller.help_manager.start_guide.bind(HelpManager.GuideType.BASIC_GUIDE)) 29 | _advanced_guide_button.pressed.connect(Controller.help_manager.start_guide.bind(HelpManager.GuideType.ADVANCED_GUIDE)) 30 | _show_shortcuts_button.pressed.connect(_show_shortcuts) 31 | 32 | if not Engine.is_editor_hint(): 33 | Controller.help_manager.reference_node(HelpManager.StepNodeRef.HELP_VIEW, get_global_rect) 34 | Controller.help_manager.reference_node(HelpManager.StepNodeRef.HELP_SHORTCUT_SHORTLIST, _short_shortcut_list.get_global_rect) 35 | 36 | 37 | func _notification(what: int) -> void: 38 | if what == NOTIFICATION_PREDELETE: 39 | if is_instance_valid(_shortcut_help): 40 | _shortcut_help.queue_free() 41 | 42 | 43 | func _show_shortcuts() -> void: 44 | # Extra size to compensate for some things. 45 | Controller.show_window_popup(_shortcut_help, _shortcut_help.custom_minimum_size + Vector2(10, 10)) 46 | -------------------------------------------------------------------------------- /gui/views/InstrumentView.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends MarginContainer 8 | 9 | @onready var _instrument_dock: ItemDock = %InstrumentDock 10 | 11 | 12 | func _ready() -> void: 13 | if not Engine.is_editor_hint(): 14 | Controller.help_manager.reference_node(HelpManager.StepNodeRef.INSTRUMENT_EDITOR_VIEW, get_global_rect) 15 | Controller.help_manager.reference_node(HelpManager.StepNodeRef.INSTRUMENT_EDITOR_DOCK, _instrument_dock.get_global_rect_with_delete_area) 16 | -------------------------------------------------------------------------------- /gui/views/InstrumentView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://cw48njslxrtsl"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/InstrumentView.gd" id="1_wchjl"] 4 | [ext_resource type="PackedScene" uid="uid://bbbxnaw4cute2" path="res://gui/widgets/ItemDock.tscn" id="2_qebn0"] 5 | [ext_resource type="Script" path="res://gui/views/instrument_view/InstrumentDock.gd" id="3_6nc7o"] 6 | [ext_resource type="PackedScene" uid="uid://1satggoreys0" path="res://gui/views/instrument_view/InstrumentSettings.tscn" id="3_88ydm"] 7 | 8 | [node name="InstrumentView" type="MarginContainer"] 9 | auto_translate_mode = 1 10 | anchors_preset = 15 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | grow_horizontal = 2 14 | grow_vertical = 2 15 | script = ExtResource("1_wchjl") 16 | 17 | [node name="Layout" type="HBoxContainer" parent="."] 18 | layout_mode = 2 19 | 20 | [node name="InstrumentDock" parent="Layout" instance=ExtResource("2_qebn0")] 21 | unique_name_in_owner = true 22 | custom_minimum_size = Vector2(380, 0) 23 | layout_mode = 2 24 | script = ExtResource("3_6nc7o") 25 | add_button_text = "ADD NEW INSTRUMENT" 26 | 27 | [node name="InstrumentSettings" parent="Layout" instance=ExtResource("3_88ydm")] 28 | unique_name_in_owner = true 29 | layout_mode = 2 30 | -------------------------------------------------------------------------------- /gui/views/LockedIndicator.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | extends Control 9 | 10 | var message: String = "PLEASE WAIT": 11 | set(value): 12 | message = value 13 | _update_label() 14 | 15 | @onready var _message_label: Label = $MessageLabel 16 | 17 | 18 | func _ready() -> void: 19 | var shader_material := (material as ShaderMaterial) 20 | shader_material.set_shader_parameter("base_color", get_theme_color("base_color", "ExportIndicator")) 21 | shader_material.set_shader_parameter("stripe_color", get_theme_color("stripe_color", "ExportIndicator")) 22 | 23 | _update_label() 24 | if not Engine.is_editor_hint(): 25 | Controller.music_player.playback_bar_changed.connect(_switch_direction) 26 | 27 | 28 | func _draw() -> void: 29 | # Gives us a canvas for the shader to do its thing. 30 | draw_rect(Rect2(Vector2.ZERO, size), Color.WHITE) 31 | 32 | 33 | func _update_label() -> void: 34 | if not is_inside_tree(): 35 | return 36 | 37 | _message_label.text = message 38 | 39 | 40 | func _switch_direction() -> void: 41 | var shader_material := (material as ShaderMaterial) 42 | var direction_angle: float = shader_material.get_shader_parameter("angle") 43 | shader_material.set_shader_parameter("angle", 1.0 - direction_angle) 44 | -------------------------------------------------------------------------------- /gui/views/LockedIndicator.gdshader: -------------------------------------------------------------------------------- 1 | /***************************************************/ 2 | /* Part of Bosca Ceoil Blue */ 3 | /* Copyright (c) 2025 Yuri Sizov and contributors */ 4 | /* Provided under MIT */ 5 | /***************************************************/ 6 | 7 | shader_type canvas_item; 8 | 9 | uniform vec4 base_color : source_color; 10 | uniform vec4 stripe_color : source_color; 11 | 12 | uniform float speed : hint_range(0.0, 100.0, 1.0) = 6; 13 | uniform float speed_factor : hint_range(-1.0, 1.0, 1.0) = -1.0; 14 | uniform float frequency : hint_range(1.0, 100.0, 0.25) = 50.0; 15 | uniform float ratio : hint_range(-1.0, 1.0, 0.1) = 0.0; 16 | uniform float angle : hint_range(0.0, 1.0, 0.01) = 1.0; 17 | 18 | void fragment() { 19 | vec3 pixel = base_color.rgb; 20 | 21 | float base_value = speed * speed_factor * TIME + frequency * UV.x * angle + frequency * UV.y * (1.0 - angle); 22 | pixel = mix(pixel, stripe_color.rgb, step(sin(abs(base_value)), ratio)); 23 | 24 | COLOR = vec4(pixel, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /gui/views/LockedIndicator.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://lnyrbw2vvjwl"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/LockedIndicator.gd" id="1_4bdb1"] 4 | [ext_resource type="Shader" path="res://gui/views/LockedIndicator.gdshader" id="1_yxryj"] 5 | 6 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_ml4mo"] 7 | shader = ExtResource("1_yxryj") 8 | shader_parameter/base_color = Color(0.101961, 0.129412, 0.137255, 1) 9 | shader_parameter/stripe_color = Color(0.203922, 0.258824, 0.27451, 1) 10 | shader_parameter/speed = 6.0 11 | shader_parameter/speed_factor = -1.0 12 | shader_parameter/frequency = 50.0 13 | shader_parameter/ratio = 1.49012e-08 14 | shader_parameter/angle = 1.0 15 | 16 | [node name="LockedIndicator" type="Control"] 17 | auto_translate_mode = 1 18 | material = SubResource("ShaderMaterial_ml4mo") 19 | layout_mode = 3 20 | anchors_preset = 15 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | grow_horizontal = 2 24 | grow_vertical = 2 25 | script = ExtResource("1_4bdb1") 26 | 27 | [node name="MessageLabel" type="Label" parent="."] 28 | layout_mode = 1 29 | anchors_preset = 8 30 | anchor_left = 0.5 31 | anchor_top = 0.5 32 | anchor_right = 0.5 33 | anchor_bottom = 0.5 34 | offset_left = -20.0 35 | offset_top = -14.0 36 | offset_right = 20.0 37 | offset_bottom = 14.0 38 | grow_horizontal = 2 39 | grow_vertical = 2 40 | text = "PLEASE WAIT" 41 | horizontal_alignment = 1 42 | vertical_alignment = 1 43 | -------------------------------------------------------------------------------- /gui/views/help_view/BackButton.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name BackButton extends Label 8 | 9 | signal pressed() 10 | 11 | var _hovered: bool = false 12 | 13 | 14 | func _ready() -> void: 15 | mouse_entered.connect(func() -> void: 16 | _hovered = true 17 | queue_redraw() 18 | ) 19 | mouse_exited.connect(func() -> void: 20 | _hovered = false 21 | queue_redraw() 22 | ) 23 | 24 | 25 | func _draw() -> void: 26 | if _hovered: 27 | var hover_color := get_theme_color("hover_color") 28 | draw_rect(Rect2(Vector2.ZERO, size), hover_color) 29 | 30 | 31 | func _gui_input(event: InputEvent) -> void: 32 | if event is InputEventMouseButton: 33 | var mb := event as InputEventMouseButton 34 | 35 | if mb.pressed && mb.button_index == MOUSE_BUTTON_LEFT: 36 | pressed.emit() 37 | -------------------------------------------------------------------------------- /gui/views/help_view/BackButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dhu2m0bp5l822"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/help_view/BackButton.gd" id="1_wmavh"] 4 | 5 | [node name="BackButton" type="Label"] 6 | custom_minimum_size = Vector2(120, 34) 7 | anchors_preset = 10 8 | anchor_right = 1.0 9 | offset_bottom = 22.2222 10 | grow_horizontal = 2 11 | mouse_filter = 0 12 | theme_type_variation = &"BackButton" 13 | text = "BACK" 14 | horizontal_alignment = 1 15 | vertical_alignment = 1 16 | script = ExtResource("1_wmavh") 17 | -------------------------------------------------------------------------------- /gui/views/help_view/ContributorLine.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name ContributorLine extends HBoxContainer 9 | 10 | const CONTRIBUTIONS_LINK := "https://github.com/YuriSizov/boscaceoil-blue/commits?author=" 11 | 12 | @export var username: String = "": 13 | set = set_username 14 | @export var contributions: int = 0: 15 | set = set_contributions 16 | 17 | @onready var _username_label: Label = $UsernameLabel 18 | @onready var _link_label: LinkLabel = $LinkLabel 19 | 20 | 21 | func _ready() -> void: 22 | _update_labels() 23 | 24 | 25 | func set_username(value: String) -> void: 26 | if username == value: 27 | return 28 | 29 | username = value 30 | _update_labels() 31 | 32 | 33 | func set_contributions(value: int) -> void: 34 | if contributions == value: 35 | return 36 | 37 | contributions = value 38 | _update_labels() 39 | 40 | 41 | func _update_labels() -> void: 42 | if not is_node_ready(): 43 | return 44 | 45 | _username_label.text = username 46 | _link_label.url = CONTRIBUTIONS_LINK + username 47 | _link_label.text = "1 contribution" if contributions == 1 else ("%s contributions" % contributions) 48 | -------------------------------------------------------------------------------- /gui/views/help_view/ContributorLine.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://duiytn30lva8o"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/help_view/ContributorLine.gd" id="1_m4ay6"] 4 | [ext_resource type="PackedScene" uid="uid://x2nsv2sot0hk" path="res://gui/widgets/LinkLabel.tscn" id="2_ru8gd"] 5 | 6 | [node name="ContributorLine" type="HBoxContainer"] 7 | auto_translate_mode = 1 8 | anchors_preset = 10 9 | anchor_right = 1.0 10 | offset_bottom = 28.0 11 | grow_horizontal = 2 12 | script = ExtResource("1_m4ay6") 13 | 14 | [node name="UsernameLabel" type="Label" parent="."] 15 | auto_translate_mode = 1 16 | layout_mode = 2 17 | size_flags_horizontal = 0 18 | theme_type_variation = &"CreditsLabelHeader" 19 | text = "username" 20 | 21 | [node name="Filler" type="Control" parent="."] 22 | auto_translate_mode = 1 23 | custom_minimum_size = Vector2(8, 0) 24 | layout_mode = 2 25 | size_flags_horizontal = 3 26 | 27 | [node name="LinkLabel" parent="." instance=ExtResource("2_ru8gd")] 28 | layout_mode = 2 29 | size_flags_horizontal = 10 30 | theme_type_variation = &"CreditsLabelLink" 31 | text = "0 contributions" 32 | -------------------------------------------------------------------------------- /gui/views/help_view/ShortcutLine.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cinwiem08gkak"] 2 | 3 | [ext_resource type="Script" path="res://gui/views/help_view/ShortcutLine.gd" id="1_06v85"] 4 | 5 | [node name="ShortcutLine" type="HBoxContainer"] 6 | auto_translate_mode = 1 7 | anchors_preset = 10 8 | anchor_right = 1.0 9 | offset_bottom = 28.0 10 | grow_horizontal = 2 11 | script = ExtResource("1_06v85") 12 | 13 | [node name="KeyLabel" type="Label" parent="."] 14 | auto_translate_mode = 1 15 | layout_mode = 2 16 | size_flags_horizontal = 0 17 | theme_type_variation = &"CreditsLabelAccented" 18 | text = "[UNBOUND]" 19 | uppercase = true 20 | 21 | [node name="Filler" type="Control" parent="."] 22 | auto_translate_mode = 1 23 | custom_minimum_size = Vector2(8, 0) 24 | layout_mode = 2 25 | size_flags_horizontal = 3 26 | 27 | [node name="DescriptionLabel" type="Label" parent="."] 28 | auto_translate_mode = 1 29 | layout_mode = 2 30 | size_flags_horizontal = 0 31 | theme_type_variation = &"CreditsLabel" 32 | text = "Description" 33 | -------------------------------------------------------------------------------- /gui/views/note_map/NoteMap.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://dciyoa2wkbtfp"] 2 | 3 | [ext_resource type="Theme" uid="uid://dkb62tku7dpgs" path="res://gui/theme/instruments/instrument_theme_gray.tres" id="1_0bvov"] 4 | [ext_resource type="Script" path="res://gui/views/note_map/NoteMap.gd" id="1_opxc8"] 5 | [ext_resource type="Script" path="res://gui/views/note_map/NoteMapGutter.gd" id="2_3knao"] 6 | [ext_resource type="Script" path="res://gui/views/note_map/NoteMapScrollbar.gd" id="3_gs7e5"] 7 | [ext_resource type="Texture2D" uid="uid://cbvbbfa1ojixi" path="res://assets/icons/arrow_up_small.png" id="6_5b2uk"] 8 | [ext_resource type="Script" path="res://gui/views/note_map/NoteMapOverlay.gd" id="6_20i0e"] 9 | [ext_resource type="Texture2D" uid="uid://n8jw4gat8tg7" path="res://assets/icons/arrow_down_small.png" id="7_oqrjh"] 10 | 11 | [node name="NoteMap" type="Control"] 12 | auto_translate_mode = 1 13 | clip_contents = true 14 | custom_minimum_size = Vector2(0, 240) 15 | layout_mode = 3 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | size_flags_vertical = 3 22 | mouse_default_cursor_shape = 2 23 | theme = ExtResource("1_0bvov") 24 | script = ExtResource("1_opxc8") 25 | 26 | [node name="NoteMapGutter" type="Control" parent="."] 27 | auto_translate_mode = 1 28 | custom_minimum_size = Vector2(50, 0) 29 | layout_mode = 1 30 | anchors_preset = 9 31 | anchor_bottom = 1.0 32 | grow_vertical = 2 33 | script = ExtResource("2_3knao") 34 | 35 | [node name="NoteMapScrollbar" type="Control" parent="."] 36 | auto_translate_mode = 1 37 | custom_minimum_size = Vector2(36, 0) 38 | layout_mode = 1 39 | anchors_preset = 11 40 | anchor_left = 1.0 41 | anchor_right = 1.0 42 | anchor_bottom = 1.0 43 | grow_horizontal = 0 44 | grow_vertical = 2 45 | script = ExtResource("3_gs7e5") 46 | 47 | [node name="NoteMapOverlay" type="Control" parent="."] 48 | layout_mode = 1 49 | anchors_preset = 15 50 | anchor_right = 1.0 51 | anchor_bottom = 1.0 52 | grow_horizontal = 2 53 | grow_vertical = 2 54 | mouse_filter = 2 55 | script = ExtResource("6_20i0e") 56 | 57 | [node name="NoteMapScrollButtons" type="Control" parent="."] 58 | auto_translate_mode = 1 59 | custom_minimum_size = Vector2(36, 0) 60 | layout_mode = 1 61 | anchors_preset = 11 62 | anchor_left = 1.0 63 | anchor_right = 1.0 64 | anchor_bottom = 1.0 65 | offset_left = -36.0 66 | grow_horizontal = 0 67 | grow_vertical = 2 68 | mouse_filter = 2 69 | 70 | [node name="UpButton" type="Button" parent="NoteMapScrollButtons"] 71 | unique_name_in_owner = true 72 | custom_minimum_size = Vector2(0, 22) 73 | layout_mode = 1 74 | anchors_preset = 10 75 | anchor_right = 1.0 76 | offset_bottom = 20.0 77 | grow_horizontal = 2 78 | focus_mode = 0 79 | theme_type_variation = &"ScrollbarButton" 80 | icon = ExtResource("6_5b2uk") 81 | icon_alignment = 1 82 | expand_icon = true 83 | 84 | [node name="DownButton" type="Button" parent="NoteMapScrollButtons"] 85 | unique_name_in_owner = true 86 | auto_translate_mode = 1 87 | custom_minimum_size = Vector2(0, 20) 88 | layout_mode = 1 89 | anchors_preset = 12 90 | anchor_top = 1.0 91 | anchor_right = 1.0 92 | anchor_bottom = 1.0 93 | offset_top = -22.0 94 | grow_horizontal = 2 95 | grow_vertical = 0 96 | focus_mode = 0 97 | theme_type_variation = &"ScrollbarButton" 98 | icon = ExtResource("7_oqrjh") 99 | icon_alignment = 1 100 | expand_icon = true 101 | -------------------------------------------------------------------------------- /gui/views/note_map/NoteMapGutter.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name NoteMapGutter extends Control 9 | 10 | signal note_preview_requested(row_index: int) 11 | 12 | var note_rows: Array[NoteMap.NoteRow] = [] 13 | var _hovered_row: int = -1 14 | 15 | 16 | func _gui_input(event: InputEvent) -> void: 17 | if event is InputEventMouseButton: 18 | var mb := event as InputEventMouseButton 19 | 20 | if mb.pressed && mb.button_index == MOUSE_BUTTON_LEFT: 21 | if _hovered_row >= 0 && _hovered_row < note_rows.size(): 22 | note_preview_requested.emit(_hovered_row) 23 | 24 | 25 | func _physics_process(_delta: float) -> void: 26 | _update_row_cursor() 27 | 28 | 29 | func _draw() -> void: 30 | var available_rect: Rect2 = get_available_rect() 31 | 32 | # Draw background. 33 | var gutter_color := get_theme_color("gutter_color", "NoteMap") 34 | 35 | draw_rect(available_rect, gutter_color) 36 | 37 | # Draw note labels. 38 | 39 | var hover_color := get_theme_color("gutter_hover_color", "NoteMap") 40 | var note_height := get_theme_constant("note_height", "NoteMap") 41 | 42 | var font := get_theme_default_font() 43 | var font_size := get_theme_default_font_size() 44 | var font_color := get_theme_color("font_color", "Label") 45 | var shadow_color := get_theme_color("shadow_color", "Label") 46 | var shadow_size := Vector2(get_theme_constant("shadow_offset_x", "Label"), get_theme_constant("shadow_offset_y", "Label")) 47 | 48 | var i := 0 49 | for note in note_rows: 50 | if i == _hovered_row: 51 | var cursor_size := Vector2(available_rect.size.x, note_height) 52 | var cursor_position := note.grid_position - cursor_size 53 | 54 | draw_rect(Rect2(cursor_position, cursor_size), hover_color) 55 | 56 | var string_position := note.label_position + Vector2(8, 0) 57 | var shadow_position := string_position + shadow_size 58 | 59 | draw_string(font, shadow_position, note.label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, shadow_color) 60 | draw_string(font, string_position, note.label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color) 61 | 62 | i += 1 63 | 64 | 65 | func get_available_rect() -> Rect2: 66 | var available_rect := Rect2(Vector2.ZERO, size) 67 | if not is_inside_tree(): 68 | return available_rect 69 | 70 | return available_rect 71 | 72 | 73 | # Hover and interactions. 74 | 75 | func _get_cell_at_cursor() -> Vector2i: 76 | var available_rect: Rect2 = get_available_rect() 77 | var note_height := get_theme_constant("note_height", "NoteMap") 78 | 79 | var mouse_position := get_local_mouse_position() 80 | if not available_rect.has_point(mouse_position): 81 | return Vector2i(-1, -1) 82 | 83 | var mouse_normalized := mouse_position - available_rect.position 84 | var cell_indexed := Vector2i(0, 0) 85 | cell_indexed.y = clampi(floori((available_rect.size.y - mouse_normalized.y) / note_height), 0, note_rows.size() - 1) 86 | return cell_indexed 87 | 88 | 89 | func _update_row_cursor() -> void: 90 | if Engine.is_editor_hint(): 91 | return 92 | 93 | var row_indexed := _get_cell_at_cursor() 94 | if row_indexed.y != _hovered_row: 95 | _hovered_row = row_indexed.y 96 | queue_redraw() 97 | -------------------------------------------------------------------------------- /gui/views/note_map/NoteMapScrollbar.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name NoteMapScrollbar extends Control 9 | 10 | signal shifted_up() 11 | signal shifted_down() 12 | signal centered() 13 | 14 | var octave_rows: Array[NoteMap.OctaveRow] = [] 15 | 16 | var _button_holder: ButtonHolder = null 17 | @onready var _up_button: Button = %UpButton 18 | @onready var _down_button: Button = %DownButton 19 | 20 | 21 | func _ready() -> void: 22 | _button_holder = ButtonHolder.new(self, _up_button, _down_button) 23 | _button_holder.set_press_callback(_emit_hold_signal) 24 | _button_holder.set_button_action(_up_button, "bosca_notemap_up") 25 | _button_holder.set_button_action(_down_button, "bosca_notemap_down") 26 | 27 | 28 | func _process(delta: float) -> void: 29 | _button_holder.process(delta) 30 | 31 | 32 | func _gui_input(event: InputEvent) -> void: 33 | if event is InputEventMouseButton: 34 | var mb := event as InputEventMouseButton 35 | 36 | if mb.button_index == MOUSE_BUTTON_LEFT && mb.pressed && mb.double_click: 37 | centered.emit() 38 | 39 | 40 | func _shortcut_input(event: InputEvent) -> void: 41 | _button_holder.input(event) 42 | 43 | 44 | func _draw() -> void: 45 | var available_rect := get_available_rect() 46 | 47 | # Draw background. 48 | var gutter_rect := Rect2(Vector2.ZERO, size) 49 | draw_rect(gutter_rect, get_theme_color("gutter_color", "NoteMap")) 50 | 51 | # Draw octave labels. 52 | 53 | var font := get_theme_default_font() 54 | var font_size := get_theme_default_font_size() 55 | var font_color := get_theme_color("font_color", "Label") 56 | var shadow_color := get_theme_color("shadow_color", "Label") 57 | var shadow_size := Vector2(get_theme_constant("shadow_offset_x", "Label"), get_theme_constant("shadow_offset_y", "Label")) 58 | 59 | for octave in octave_rows: 60 | var octave_string := "%d" % (octave.octave_index + 1) 61 | var string_size := font.get_string_size(octave_string, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size) 62 | 63 | var string_position := octave.label_position + Vector2(available_rect.size.x - string_size.x - 4, 0) 64 | var shadow_position := string_position + shadow_size 65 | 66 | draw_string(font, shadow_position, octave_string, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, shadow_color) 67 | draw_string(font, string_position, octave_string, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, font_color) 68 | 69 | 70 | func get_available_rect() -> Rect2: 71 | var available_rect := Rect2(Vector2.ZERO, size) 72 | if not is_inside_tree(): 73 | return available_rect 74 | 75 | if _up_button: 76 | available_rect.position.y += _up_button.size.y 77 | available_rect.size.y -= _up_button.size.y 78 | if _down_button: 79 | available_rect.size.y -= _down_button.size.y 80 | 81 | return available_rect 82 | 83 | 84 | func _emit_hold_signal(hold_button: Button) -> void: 85 | if not hold_button: 86 | return 87 | 88 | if hold_button == _up_button: 89 | shifted_up.emit() 90 | elif hold_button == _down_button: 91 | shifted_down.emit() 92 | -------------------------------------------------------------------------------- /gui/views/pattern_map/PatternMapOverlay.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | extends Control 9 | 10 | var pattern_height: float = 0 11 | var pattern_width: float = 0 12 | var playback_cursor_position: float = -1 13 | var pattern_cursor_position: Vector2 = Vector2i(-1, -1) 14 | 15 | 16 | func _draw() -> void: 17 | var border_width := get_theme_constant("border_width", "PatternMap") 18 | var half_border_width := float(border_width) / 2.0 19 | 20 | # Draw the playback cursor. 21 | 22 | if playback_cursor_position >= 0: 23 | var playback_cursor_width := get_theme_constant("playback_cursor_width", "NoteMap") 24 | 25 | var cursor_position := Vector2(playback_cursor_position, 0) 26 | var cursor_size := Vector2(playback_cursor_width, size.y) 27 | var cursor_color := get_theme_color("playback_cursor_color", "NoteMap") 28 | 29 | var half_cursor_width := float(playback_cursor_width) / 2.0 30 | var cursor_bevel_position := Vector2(playback_cursor_position + half_cursor_width, 0) 31 | var cursor_bevel_size := Vector2(half_cursor_width, size.y) 32 | var cursor_bevel_color := get_theme_color("playback_cursor_bevel_color", "NoteMap") 33 | 34 | draw_rect(Rect2(cursor_position, cursor_size), cursor_color) 35 | draw_rect(Rect2(cursor_bevel_position, cursor_bevel_size), cursor_bevel_color) 36 | 37 | # Draw the pattern cursor. 38 | 39 | if pattern_width > 0 && pattern_cursor_position.x >= 0 && pattern_cursor_position.y >= 0: 40 | var pattern_position := pattern_cursor_position + Vector2(half_border_width, half_border_width) 41 | var pattern_size := Vector2(pattern_width, pattern_height) - Vector2(border_width, border_width) 42 | var pattern_cursor_color := get_theme_color("note_cursor_color", "NoteMap") 43 | var pattern_cursor_width := get_theme_constant("note_cursor_width", "NoteMap") 44 | 45 | draw_rect(Rect2(pattern_position, pattern_size), pattern_cursor_color, false, pattern_cursor_width) 46 | -------------------------------------------------------------------------------- /gui/widgets/AccentedContentEffect.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name AccentedContentEffect extends RichTextEffect 9 | 10 | var bbcode: String = "accent" 11 | 12 | 13 | func _process_custom_fx(char_fx: CharFXTransform) -> bool: 14 | char_fx.color = ThemeDB.get_project_theme().get_color("accent_color", "InfoPopup") 15 | return true 16 | -------------------------------------------------------------------------------- /gui/widgets/AnimatedLogo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://jjkaxpgl6mmu"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/AnimatedLogo.gd" id="1_i10nm"] 4 | [ext_resource type="Texture2D" uid="uid://bl2bn0gj2t4wo" path="res://assets/logos/logo_blue.png" id="1_lc2ol"] 5 | [ext_resource type="Texture2D" uid="uid://uxa5unpo0xp8" path="res://assets/logos/logo_shadow.png" id="2_w1mr7"] 6 | 7 | [node name="LogoContainer" type="Control"] 8 | layout_mode = 3 9 | anchors_preset = 15 10 | anchor_right = 1.0 11 | anchor_bottom = 1.0 12 | grow_horizontal = 2 13 | grow_vertical = 2 14 | mouse_filter = 2 15 | script = ExtResource("1_i10nm") 16 | 17 | [node name="Logo" type="TextureRect" parent="."] 18 | auto_translate_mode = 1 19 | texture_filter = 1 20 | custom_minimum_size = Vector2(0, 72) 21 | layout_mode = 1 22 | anchors_preset = 15 23 | anchor_right = 1.0 24 | anchor_bottom = 1.0 25 | grow_horizontal = 2 26 | grow_vertical = 2 27 | mouse_filter = 2 28 | texture = ExtResource("1_lc2ol") 29 | expand_mode = 1 30 | stretch_mode = 5 31 | 32 | [node name="LogoShadow" type="TextureRect" parent="Logo"] 33 | auto_translate_mode = 1 34 | show_behind_parent = true 35 | texture_filter = 1 36 | custom_minimum_size = Vector2(0, 72) 37 | layout_mode = 1 38 | anchors_preset = 15 39 | anchor_right = 1.0 40 | anchor_bottom = 1.0 41 | offset_left = 8.0 42 | offset_top = 8.0 43 | offset_right = 8.0 44 | offset_bottom = 8.0 45 | grow_horizontal = 2 46 | grow_vertical = 2 47 | mouse_filter = 2 48 | texture = ExtResource("2_w1mr7") 49 | expand_mode = 1 50 | stretch_mode = 5 51 | -------------------------------------------------------------------------------- /gui/widgets/ButtonHolder.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name ButtonHolder extends RefCounted 8 | 9 | ## Stepper is accelerated to make it easier to scroll through the range of values. 10 | ## These are arbitrary fine-tuned values. 11 | const HOLD_THRESHOLDS := [ 0.4, 0.36, 0.3, 0.2, 0.088, 0.042 ] 12 | 13 | var _owner: Control = null 14 | var _press_callback: Callable 15 | var _release_callback: Callable 16 | var _button_action_map: Dictionary = {} 17 | 18 | var _current_threshold_idx := 0 19 | var _hold_button: Button = null 20 | var _hold_interval: float = 0 21 | 22 | 23 | func _init(owner: Control, first_button: Button, second_button: Button) -> void: 24 | _owner = owner 25 | _owner.set_process(false) 26 | 27 | if is_instance_valid(first_button): 28 | first_button.button_down.connect(_button_press_started.bind(first_button)) 29 | first_button.button_up.connect(_button_press_stopped) 30 | 31 | if is_instance_valid(second_button): 32 | second_button.button_down.connect(_button_press_started.bind(second_button)) 33 | second_button.button_up.connect(_button_press_stopped) 34 | 35 | 36 | func set_press_callback(callback: Callable) -> void: 37 | _press_callback = callback 38 | 39 | 40 | func set_release_callback(callback: Callable) -> void: 41 | _release_callback = callback 42 | 43 | 44 | func set_button_action(button: Button, action_name: String) -> void: 45 | _button_action_map[action_name] = button 46 | 47 | 48 | func process(delta: float) -> void: 49 | if not _hold_button: 50 | return 51 | 52 | _hold_interval += delta 53 | if _hold_interval >= HOLD_THRESHOLDS[_current_threshold_idx]: 54 | _hold_interval = 0 55 | if _current_threshold_idx < (HOLD_THRESHOLDS.size() - 1): 56 | _current_threshold_idx += 1 57 | 58 | if _press_callback.is_valid(): 59 | _press_callback.call(_hold_button) 60 | 61 | 62 | func input(event: InputEvent, only_release: bool = false) -> void: 63 | for action_name: String in _button_action_map: 64 | if not event.is_action(action_name, true): 65 | continue 66 | 67 | var event_pressed := event.is_action_pressed(action_name, true, true) 68 | var action_button: Button = _button_action_map[action_name] 69 | 70 | # The button is already being tracked as pressed. 71 | if event_pressed && _hold_button == action_button: 72 | return 73 | 74 | # No button or a different button is being pressed, switch. 75 | if event_pressed && not only_release: 76 | _button_press_started(action_button) 77 | return 78 | 79 | # The pressed button is being released. 80 | if not event_pressed && _hold_button == action_button: 81 | _button_press_stopped() 82 | return 83 | 84 | 85 | func _button_press_started(button: Button) -> void: 86 | _current_threshold_idx = 0 87 | _hold_button = button 88 | 89 | if _press_callback.is_valid(): 90 | _press_callback.call(_hold_button) 91 | 92 | _owner.set_process(true) 93 | 94 | 95 | func _button_press_stopped() -> void: 96 | _owner.set_process(false) 97 | 98 | if _release_callback.is_valid(): 99 | _release_callback.call(_hold_button) 100 | 101 | _hold_button = null 102 | _hold_interval = 0 103 | -------------------------------------------------------------------------------- /gui/widgets/DeleteArea.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name DeleteArea extends Label 8 | 9 | const FADE_DURATION := 0.06 10 | 11 | var _hovered: bool = false 12 | var _tween: Tween = null 13 | 14 | 15 | func _ready() -> void: 16 | offset_top = -size.y 17 | 18 | mouse_entered.connect(func() -> void: 19 | _hovered = true 20 | text = "DROP TO CONFIRM" 21 | queue_redraw() 22 | ) 23 | mouse_exited.connect(func() -> void: 24 | _hovered = false 25 | text = "DELETE?" 26 | queue_redraw() 27 | ) 28 | 29 | 30 | func _draw() -> void: 31 | if _hovered: 32 | var hover_color := get_theme_color("hover_color") 33 | draw_rect(Rect2(Vector2.ZERO, size), hover_color) 34 | 35 | 36 | func fade_in() -> void: 37 | if _tween: 38 | _tween.kill() 39 | 40 | _tween = get_tree().create_tween() 41 | _tween.tween_property(self, "offset_top", 0, FADE_DURATION) 42 | _tween.tween_callback(set_mouse_filter.bind(MOUSE_FILTER_STOP)) 43 | 44 | 45 | func fade_out() -> void: 46 | if _tween: 47 | _tween.kill() 48 | 49 | _tween = get_tree().create_tween() 50 | _tween.tween_property(self, "offset_top", -size.y, FADE_DURATION) 51 | _tween.tween_callback(set_mouse_filter.bind(MOUSE_FILTER_IGNORE)) 52 | -------------------------------------------------------------------------------- /gui/widgets/DeleteArea.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bgyn67pa8s3kd"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/DeleteArea.gd" id="1_h3xfb"] 4 | 5 | [node name="DeleteArea" type="Label"] 6 | auto_translate_mode = 1 7 | show_behind_parent = true 8 | custom_minimum_size = Vector2(0, 34) 9 | anchors_preset = 10 10 | anchor_right = 1.0 11 | offset_bottom = 34.0 12 | grow_horizontal = 2 13 | theme_type_variation = &"DeleteArea" 14 | text = "DELETE?" 15 | horizontal_alignment = 1 16 | vertical_alignment = 1 17 | script = ExtResource("1_h3xfb") 18 | -------------------------------------------------------------------------------- /gui/widgets/FilePathLabel.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://ckt2fd1qbsk7f"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/FilePathLabel.gd" id="1_2ugrn"] 4 | 5 | [node name="FilePathLabel" type="Control"] 6 | custom_minimum_size = Vector2(60, 0) 7 | layout_mode = 3 8 | anchors_preset = 0 9 | offset_right = 60.0 10 | offset_bottom = 23.0 11 | theme_type_variation = &"FilePathLabel" 12 | script = ExtResource("1_2ugrn") 13 | -------------------------------------------------------------------------------- /gui/widgets/FillerControl.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | # A trick control to reserve some space in a container for another control located 8 | # in another branch of the scene tree. This can be used to, for example, make the 9 | # first element inside of a box container to display above the second one. 10 | extends Control 11 | 12 | @export var paired_node: Control 13 | 14 | 15 | func _get_minimum_size() -> Vector2: 16 | if not paired_node: 17 | return Vector2.ZERO 18 | 19 | return paired_node.get_combined_minimum_size() 20 | -------------------------------------------------------------------------------- /gui/widgets/HighlightManager.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | extends CanvasLayer 8 | 9 | const FADE_BLINKS := 4 10 | const BLINK_IN_DURATION := 0.1 11 | const BLICK_HOLD_DURATION := 0.05 12 | const BLINK_OUT_DURATION := 0.05 13 | 14 | var highlight_rect_getter: Callable: 15 | set = set_highlight_rect_getter 16 | var _highlight_rect_owner: Control = null 17 | 18 | var _fade_tween: Tween = null 19 | 20 | @onready var _indicator: Control = $HighlightIndicator 21 | 22 | 23 | func _ready() -> void: 24 | _indicator.draw.connect(_draw_highlight_indicator) 25 | 26 | 27 | func _draw_highlight_indicator() -> void: 28 | if not highlight_rect_getter.is_valid(): 29 | return 30 | 31 | var highlight_color := _indicator.get_theme_color("highlight_color", "HighlightManager") 32 | var highlight_bevel_color := _indicator.get_theme_color("highlight_bevel_color", "HighlightManager") 33 | var highlight_bevel_thickness := _indicator.get_theme_constant("highlight_thickness", "HighlightManager") 34 | var highlight_thickness := highlight_bevel_thickness / 2.0 35 | 36 | var highlight_bevel_rect: Rect2 = highlight_rect_getter.call() 37 | var highlight_rect := highlight_bevel_rect.grow(-highlight_thickness / 2.0) # Unfilled rect draws its border centered. 38 | 39 | _indicator.draw_rect(highlight_bevel_rect, highlight_bevel_color, false, highlight_bevel_thickness) 40 | _indicator.draw_rect(highlight_rect, highlight_color, false, highlight_thickness) 41 | 42 | 43 | func set_highlight_rect_getter(value: Callable) -> void: 44 | if highlight_rect_getter == value: 45 | return # Avoid double fading the same highlight for subsequent help steps. 46 | 47 | if _highlight_rect_owner: 48 | _highlight_rect_owner.resized.disconnect(update_highlight) 49 | 50 | highlight_rect_getter = value 51 | if highlight_rect_getter.is_valid(): 52 | _highlight_rect_owner = highlight_rect_getter.get_object() as Control 53 | 54 | if _highlight_rect_owner: 55 | _highlight_rect_owner.resized.connect(update_highlight) 56 | 57 | if _fade_tween: 58 | _fade_tween.kill() 59 | 60 | _fade_tween = get_tree().create_tween() 61 | _indicator.self_modulate.a = 0.0 62 | 63 | for i in FADE_BLINKS: 64 | _fade_tween.tween_property(_indicator, "self_modulate:a", 1.0, BLINK_IN_DURATION).set_delay(BLICK_HOLD_DURATION) 65 | _fade_tween.tween_property(_indicator, "self_modulate:a", 0.0, BLINK_OUT_DURATION).set_delay(BLICK_HOLD_DURATION) 66 | 67 | _fade_tween.tween_property(_indicator, "self_modulate:a", 1.0, BLINK_IN_DURATION) 68 | 69 | 70 | func update_highlight() -> void: 71 | _indicator.queue_redraw() 72 | -------------------------------------------------------------------------------- /gui/widgets/ItemDock.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://bbbxnaw4cute2"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/ItemDock.gd" id="1_cdtng"] 4 | [ext_resource type="PackedScene" uid="uid://bgyn67pa8s3kd" path="res://gui/widgets/DeleteArea.tscn" id="2_gogr1"] 5 | [ext_resource type="PackedScene" uid="uid://71sw1mddco3" path="res://gui/widgets/SquishyButton.tscn" id="3_g3e56"] 6 | 7 | [node name="ItemDock" type="Control"] 8 | auto_translate_mode = 1 9 | custom_minimum_size = Vector2(400, 0) 10 | layout_mode = 3 11 | anchors_preset = 15 12 | anchor_right = 1.0 13 | anchor_bottom = 1.0 14 | grow_horizontal = 2 15 | grow_vertical = 2 16 | script = ExtResource("1_cdtng") 17 | 18 | [node name="DeleteArea" parent="." instance=ExtResource("2_gogr1")] 19 | unique_name_in_owner = true 20 | layout_mode = 1 21 | anchors_preset = -1 22 | anchor_top = 1.0 23 | anchor_bottom = 1.0 24 | offset_left = 16.0 25 | offset_right = -16.0 26 | offset_bottom = 0.0 27 | 28 | [node name="AddItem" parent="." instance=ExtResource("3_g3e56")] 29 | unique_name_in_owner = true 30 | layout_mode = 1 31 | anchors_preset = -1 32 | anchor_left = 0.5 33 | anchor_top = 1.0 34 | anchor_right = 0.5 35 | anchor_bottom = 1.0 36 | offset_left = -84.0 37 | offset_top = -36.0 38 | offset_right = 84.0 39 | offset_bottom = -8.0 40 | grow_horizontal = 2 41 | grow_vertical = 0 42 | text = "ADD ITEM" 43 | -------------------------------------------------------------------------------- /gui/widgets/LinkLabel.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name LinkLabel extends Label 9 | 10 | ## URL opened by clicking the label. 11 | @export var url: String = "" 12 | 13 | 14 | func _gui_input(event: InputEvent) -> void: 15 | if event is InputEventMouseButton: 16 | var mb := event as InputEventMouseButton 17 | if mb.pressed && mb.button_index == MOUSE_BUTTON_LEFT: 18 | OS.shell_open(url) 19 | -------------------------------------------------------------------------------- /gui/widgets/LinkLabel.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://x2nsv2sot0hk"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/LinkLabel.gd" id="1_3t751"] 4 | 5 | [node name="LinkLabel" type="Label"] 6 | auto_translate_mode = 1 7 | offset_right = 1.0 8 | offset_bottom = 28.0 9 | mouse_filter = 0 10 | mouse_default_cursor_shape = 2 11 | script = ExtResource("1_3t751") 12 | -------------------------------------------------------------------------------- /gui/widgets/NavigationButton.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | extends Button 9 | 10 | @export var label_text: String = "": 11 | set(value): 12 | label_text = value 13 | _update_label() 14 | 15 | @onready var _label: Label = $Label 16 | 17 | 18 | func _ready() -> void: 19 | _label.offset_left = get_theme_stylebox("normal").get_margin(SIDE_LEFT) 20 | _label.offset_right = -get_theme_stylebox("normal").get_margin(SIDE_RIGHT) 21 | _label.offset_top = get_theme_stylebox("normal").get_margin(SIDE_TOP) 22 | _label.offset_bottom = -get_theme_stylebox("normal").get_margin(SIDE_BOTTOM) 23 | 24 | _update_label() 25 | 26 | 27 | func _notification(what: int) -> void: 28 | if what == NOTIFICATION_THEME_CHANGED: 29 | _update_label_color() 30 | elif what == NOTIFICATION_EDITOR_PRE_SAVE: 31 | _clear_label_color() 32 | elif what == NOTIFICATION_EDITOR_POST_SAVE: 33 | _update_label_color() 34 | 35 | 36 | func _update_label() -> void: 37 | if not is_node_ready(): 38 | return 39 | 40 | _update_label_color() 41 | _label.text = label_text 42 | _label.queue_redraw() 43 | 44 | 45 | func _update_label_color() -> void: 46 | if not is_node_ready(): 47 | return 48 | 49 | var text_color := get_theme_color("font_color") 50 | if not button_pressed: 51 | text_color = get_theme_color("font_inactive_color") 52 | 53 | _label.add_theme_color_override("font_color", text_color) 54 | 55 | 56 | func _clear_label_color() -> void: 57 | if not is_node_ready(): 58 | return 59 | 60 | _label.remove_theme_color_override("font_color") 61 | 62 | 63 | func _toggled(_toggled_on: bool) -> void: 64 | _update_label() 65 | -------------------------------------------------------------------------------- /gui/widgets/NavigationButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://7lvvn5lu3ywt"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/NavigationButton.gd" id="1_qmiek"] 4 | 5 | [node name="NavigationButton" type="Button"] 6 | anchors_preset = 15 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | grow_horizontal = 2 10 | grow_vertical = 2 11 | theme_type_variation = &"NavigationButton" 12 | toggle_mode = true 13 | script = ExtResource("1_qmiek") 14 | 15 | [node name="Label" type="Label" parent="."] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | offset_left = 24.0 21 | offset_top = 1.0 22 | offset_right = -24.0 23 | offset_bottom = -1.0 24 | grow_horizontal = 2 25 | grow_vertical = 2 26 | theme_type_variation = &"NavigationLabel" 27 | uppercase = true 28 | -------------------------------------------------------------------------------- /gui/widgets/OptionPicker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dpno0cpboqdm0"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/OptionPicker.gd" id="1_m580c"] 4 | [ext_resource type="Texture2D" uid="uid://dltrbt3gaisqw" path="res://assets/icons/arrow_down.png" id="2_5j6us"] 5 | 6 | [node name="OptionPicker" type="MarginContainer"] 7 | offset_right = 105.0 8 | offset_bottom = 23.0 9 | mouse_filter = 0 10 | theme_type_variation = &"OptionPicker" 11 | script = ExtResource("1_m580c") 12 | placeholder_text = "Picker" 13 | 14 | [node name="Layout" type="HBoxContainer" parent="."] 15 | layout_mode = 2 16 | mouse_filter = 2 17 | theme_type_variation = &"OptionPickerBox" 18 | 19 | [node name="Arrow" type="TextureRect" parent="Layout"] 20 | custom_minimum_size = Vector2(22, 28) 21 | layout_mode = 2 22 | size_flags_vertical = 4 23 | texture = ExtResource("2_5j6us") 24 | expand_mode = 1 25 | stretch_mode = 5 26 | 27 | [node name="Label" type="Label" parent="Layout"] 28 | layout_mode = 2 29 | size_flags_horizontal = 3 30 | text = "Picker" 31 | vertical_alignment = 1 32 | -------------------------------------------------------------------------------- /gui/widgets/PadSlider.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dtvff0pavdph7"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/PadSlider.gd" id="1_j35o1"] 4 | 5 | [node name="PadSlider" type="Control"] 6 | auto_translate_mode = 1 7 | layout_mode = 3 8 | anchors_preset = 15 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 2 13 | script = ExtResource("1_j35o1") 14 | -------------------------------------------------------------------------------- /gui/widgets/SquishyButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://71sw1mddco3"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/SquishyButton.gd" id="1_jsx4w"] 4 | 5 | [node name="SquishyButton" type="BaseButton"] 6 | _import_path = NodePath("") 7 | unique_name_in_owner = false 8 | process_mode = 0 9 | process_priority = 0 10 | process_physics_priority = 0 11 | process_thread_group = 0 12 | physics_interpolation_mode = 2 13 | auto_translate_mode = 1 14 | editor_description = "" 15 | visible = true 16 | modulate = Color(1, 1, 1, 1) 17 | self_modulate = Color(1, 1, 1, 1) 18 | show_behind_parent = false 19 | top_level = false 20 | clip_children = 0 21 | light_mask = 1 22 | visibility_layer = 1 23 | z_index = 0 24 | z_as_relative = true 25 | y_sort_enabled = false 26 | texture_filter = 0 27 | texture_repeat = 0 28 | material = null 29 | use_parent_material = false 30 | clip_contents = false 31 | custom_minimum_size = Vector2(0, 0) 32 | layout_direction = 0 33 | layout_mode = 3 34 | anchors_preset = 0 35 | anchor_left = 0.0 36 | anchor_top = 0.0 37 | anchor_right = 0.0 38 | anchor_bottom = 0.0 39 | offset_left = 0.0 40 | offset_top = 0.0 41 | offset_right = 48.0 42 | offset_bottom = 8.0 43 | grow_horizontal = 1 44 | grow_vertical = 1 45 | rotation = 0.0 46 | scale = Vector2(1, 1) 47 | pivot_offset = Vector2(0, 0) 48 | size_flags_horizontal = 1 49 | size_flags_vertical = 1 50 | size_flags_stretch_ratio = 1.0 51 | localize_numeral_system = true 52 | tooltip_text = "" 53 | focus_neighbor_left = NodePath("") 54 | focus_neighbor_top = NodePath("") 55 | focus_neighbor_right = NodePath("") 56 | focus_neighbor_bottom = NodePath("") 57 | focus_next = NodePath("") 58 | focus_previous = NodePath("") 59 | focus_mode = 0 60 | mouse_filter = 0 61 | mouse_force_pass_scroll_events = true 62 | mouse_default_cursor_shape = 0 63 | theme = null 64 | theme_type_variation = &"" 65 | disabled = false 66 | toggle_mode = false 67 | button_pressed = false 68 | action_mode = 1 69 | button_mask = 1 70 | keep_pressed_outside = false 71 | button_group = null 72 | shortcut = null 73 | shortcut_feedback = true 74 | shortcut_in_tooltip = true 75 | script = ExtResource("1_jsx4w") 76 | -------------------------------------------------------------------------------- /gui/widgets/Stepper.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name Stepper extends HBoxContainer 9 | 10 | signal value_changed() 11 | 12 | var _value: int = 0 13 | @export var value: int = 0: 14 | get = _get_value, 15 | set = _set_value 16 | @export var min_value: int = 0 17 | @export var max_value: int = 1 18 | @export var step: int = 1 19 | 20 | var _button_holder: ButtonHolder = null 21 | @onready var _increment_button: Button = $Increase 22 | @onready var _decrement_button: Button = $Decrease 23 | @onready var _label: Label = $Label 24 | 25 | 26 | func _ready() -> void: 27 | _ensure_label_size() 28 | _update_value_label() 29 | _update_buttons() 30 | 31 | _button_holder = ButtonHolder.new(self, _increment_button, _decrement_button) 32 | _button_holder.set_press_callback(_change_value_on_hold) 33 | _button_holder.set_release_callback(_emit_changed_on_release) 34 | 35 | 36 | func _process(delta: float) -> void: 37 | _button_holder.process(delta) 38 | 39 | 40 | func _ensure_label_size() -> void: 41 | if not is_inside_tree(): 42 | return 43 | 44 | var string_size := 0 45 | for num : int in [ _value, min_value, max_value ]: 46 | var num_size: int = ("%d" % num).length() 47 | if num_size > string_size: 48 | string_size = num_size 49 | 50 | var label_font := _label.get_theme_font("font") 51 | var label_font_size := _label.get_theme_font_size("font_size") 52 | var target_size := label_font.get_string_size("0".repeat(string_size), HORIZONTAL_ALIGNMENT_CENTER, -1, label_font_size).x 53 | _label.custom_minimum_size.x = target_size 54 | 55 | 56 | func _update_value_label() -> void: 57 | if not is_inside_tree(): 58 | return 59 | 60 | _label.text = "%d" % _value 61 | 62 | 63 | func _update_buttons() -> void: 64 | if not is_inside_tree(): 65 | return 66 | 67 | _increment_button.disabled = (_value == max_value) 68 | _decrement_button.disabled = (_value == min_value) 69 | 70 | 71 | func _get_value() -> int: 72 | return _value 73 | 74 | 75 | func _set_value(next_value: int) -> void: 76 | _set_value_silent(next_value) 77 | _update_buttons() 78 | 79 | 80 | func _set_value_silent(next_value: int) -> void: 81 | if _value == next_value: 82 | return 83 | 84 | _value = next_value 85 | _update_value_label() 86 | 87 | 88 | func _change_value_on_hold(hold_button: Button) -> void: 89 | var delta_sign := 0 90 | if hold_button == _increment_button: 91 | delta_sign = 1 92 | elif hold_button == _decrement_button: 93 | delta_sign = -1 94 | 95 | if delta_sign == 0: 96 | return 97 | 98 | var raw_value := _value + delta_sign * step 99 | # Round to the closest step value, then clamp into the limit. 100 | @warning_ignore("integer_division") 101 | var next_value := clampi((raw_value / step) * step, min_value, max_value) 102 | _set_value_silent(next_value) 103 | 104 | 105 | func _emit_changed_on_release(_hold_button: Button) -> void: 106 | _update_buttons() 107 | value_changed.emit() 108 | -------------------------------------------------------------------------------- /gui/widgets/Stepper.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://d2peohvka6nmb"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/Stepper.gd" id="1_ng8l4"] 4 | [ext_resource type="Texture2D" uid="uid://cypx2wsn8bvf2" path="res://assets/icons/arrow_left.png" id="2_c75wn"] 5 | [ext_resource type="Texture2D" uid="uid://bl0lbiq37a2o2" path="res://assets/icons/arrow_right.png" id="3_aqd2k"] 6 | 7 | [node name="Stepper" type="HBoxContainer"] 8 | offset_right = 82.0 9 | offset_bottom = 32.0 10 | theme_type_variation = &"StepperBox" 11 | alignment = 1 12 | script = ExtResource("1_ng8l4") 13 | 14 | [node name="Decrease" type="Button" parent="."] 15 | custom_minimum_size = Vector2(28, 28) 16 | layout_mode = 2 17 | size_flags_vertical = 4 18 | focus_mode = 0 19 | theme_type_variation = &"StepperButton" 20 | disabled = true 21 | icon = ExtResource("2_c75wn") 22 | flat = true 23 | icon_alignment = 1 24 | expand_icon = true 25 | 26 | [node name="Label" type="Label" parent="."] 27 | custom_minimum_size = Vector2(14, 0) 28 | layout_mode = 2 29 | text = "0" 30 | horizontal_alignment = 1 31 | vertical_alignment = 1 32 | 33 | [node name="Increase" type="Button" parent="."] 34 | auto_translate_mode = 1 35 | custom_minimum_size = Vector2(28, 28) 36 | layout_mode = 2 37 | size_flags_vertical = 4 38 | focus_mode = 0 39 | theme_type_variation = &"StepperButton" 40 | icon = ExtResource("3_aqd2k") 41 | flat = true 42 | icon_alignment = 1 43 | expand_icon = true 44 | -------------------------------------------------------------------------------- /gui/widgets/ToastMessage.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | extends Control 9 | 10 | const LABEL_EXTRA_PADDING := 6 11 | const FADE_IN_DURATION := 0.2 12 | const FADE_OUT_DURATION := 0.1 13 | 14 | var _tween: Tween = null 15 | var _time_remaining: float = 0.0 16 | var _label_offset: float = 0.0 17 | 18 | @onready var _label: Label = $Label 19 | 20 | 21 | func _ready() -> void: 22 | _label_offset = _label.get_combined_minimum_size().y 23 | _label.offset_top = LABEL_EXTRA_PADDING 24 | _label.offset_bottom = LABEL_EXTRA_PADDING + _label_offset 25 | 26 | if not Engine.is_editor_hint(): 27 | Controller.status_updated.connect(show_message) 28 | 29 | 30 | func _physics_process(delta: float) -> void: 31 | _time_remaining -= delta 32 | if _time_remaining <= 0: 33 | hide_message() 34 | return 35 | 36 | 37 | func _get_minimum_size() -> Vector2: 38 | if not is_node_ready(): 39 | return Vector2.ZERO 40 | 41 | return _label.get_combined_minimum_size() 42 | 43 | 44 | func show_message(severity: Controller.StatusLevel, text: String, duration: float = 3.0) -> void: 45 | if _tween: 46 | _tween.kill() 47 | _tween = get_tree().create_tween() 48 | _tween.set_parallel(true) 49 | 50 | # Hide existing message, if present. 51 | if _time_remaining > 0: 52 | _tween.tween_property(_label, "offset_top", LABEL_EXTRA_PADDING, FADE_OUT_DURATION) 53 | _tween.tween_property(_label, "offset_bottom", LABEL_EXTRA_PADDING + _label_offset, FADE_OUT_DURATION) 54 | 55 | _tween.tween_callback(func() -> void: 56 | _label.text = text 57 | _label.theme_type_variation = _get_severity_theme_variation(severity) 58 | ).set_delay(FADE_OUT_DURATION) 59 | _tween.tween_property(_label, "offset_top", 0, FADE_IN_DURATION).set_delay(FADE_OUT_DURATION) 60 | _tween.tween_property(_label, "offset_bottom", 0, FADE_IN_DURATION).set_delay(FADE_OUT_DURATION) 61 | else: 62 | _label.text = text 63 | _label.theme_type_variation = _get_severity_theme_variation(severity) 64 | _tween.tween_property(_label, "offset_top", 0, FADE_IN_DURATION) 65 | _tween.tween_property(_label, "offset_bottom", 0, FADE_IN_DURATION) 66 | 67 | _time_remaining = duration 68 | set_physics_process(true) 69 | 70 | 71 | func hide_message() -> void: 72 | if _tween: 73 | _tween.kill() 74 | _tween = get_tree().create_tween() 75 | _tween.set_parallel(true) 76 | 77 | _tween.tween_property(_label, "offset_top", LABEL_EXTRA_PADDING, FADE_OUT_DURATION) 78 | _tween.tween_property(_label, "offset_bottom", LABEL_EXTRA_PADDING + _label_offset, FADE_OUT_DURATION) 79 | 80 | _time_remaining = 0 81 | set_physics_process(false) 82 | 83 | 84 | func _get_severity_theme_variation(severity: Controller.StatusLevel) -> String: 85 | match severity: 86 | Controller.StatusLevel.SUCCESS: 87 | return "ToastMessageSuccess" 88 | Controller.StatusLevel.WARNING: 89 | return "ToastMessageWarning" 90 | Controller.StatusLevel.ERROR: 91 | return "ToastMessageError" 92 | 93 | return "ToastMessageDefault" 94 | -------------------------------------------------------------------------------- /gui/widgets/ToastMessage.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dxtku0ujhowt5"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/ToastMessage.gd" id="1_jh7g8"] 4 | 5 | [node name="ToastMessage" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 12 8 | anchor_top = 1.0 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 0 13 | mouse_filter = 2 14 | script = ExtResource("1_jh7g8") 15 | 16 | [node name="Label" type="Label" parent="."] 17 | auto_translate_mode = 1 18 | custom_minimum_size = Vector2(0, 30) 19 | layout_mode = 1 20 | anchors_preset = -1 21 | anchor_top = 1.0 22 | anchor_right = 1.0 23 | anchor_bottom = 1.0 24 | offset_top = 6.0 25 | offset_bottom = 36.0 26 | grow_horizontal = 2 27 | grow_vertical = 0 28 | size_flags_vertical = 8 29 | mouse_filter = 0 30 | theme_type_variation = &"ToastMessageDefault" 31 | text = "MESSAGE!" 32 | horizontal_alignment = 1 33 | vertical_alignment = 1 34 | -------------------------------------------------------------------------------- /gui/widgets/popups/CreditsPopup.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name CreditsPopup extends WindowPopup 9 | 10 | # TODO: Perhaps use an external text file for this. 11 | # Use the case-independent alphabetical order! 12 | const CONTRIBUTORS := [ 13 | # username, contributions 14 | [ "genderfreak", 1, ], 15 | [ "SirLich", 1, ], 16 | ] 17 | 18 | const CONTRIBUTOR_LINE_SCENE := preload("res://gui/views/help_view/ContributorLine.tscn") 19 | 20 | @onready var _contributors_list: VBoxContainer = %ContributorsList 21 | 22 | 23 | func _ready() -> void: 24 | super() 25 | 26 | _update_contributors_list() 27 | 28 | 29 | func _notification(what: int) -> void: 30 | if what == NOTIFICATION_SCENE_INSTANTIATED: 31 | # Since this is a part of an instantiated scene, these nodes are immediately available. 32 | # This allows us to use them safely before ready. 33 | _contributors_list = %ContributorsList 34 | 35 | 36 | func _update_contributors_list() -> void: 37 | while _contributors_list.get_child_count() > 0: 38 | var child_node := _contributors_list.get_child(0) 39 | _contributors_list.remove_child(child_node) 40 | 41 | for contributor: Array in CONTRIBUTORS: 42 | var line := CONTRIBUTOR_LINE_SCENE.instantiate() 43 | line.username = contributor[0] 44 | line.contributions = contributor[1] 45 | 46 | _contributors_list.add_child(line) 47 | -------------------------------------------------------------------------------- /gui/widgets/popups/ImportMasterPopup.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name ImportMasterPopup extends WindowPopup 9 | 10 | enum ImportType { 11 | NONE, 12 | IMPORT_MIDI, 13 | } 14 | 15 | var _current_file: String = "" 16 | 17 | @onready var _select_file_button: SquishyButton = %SelectFile 18 | @onready var _file_path_label: FilePathLabel = %FilePathLabel 19 | @onready var _pattern_size_value: Stepper = %PatternSizeValue 20 | 21 | 22 | func _ready() -> void: 23 | super() 24 | 25 | _select_file_button.pressed.connect(_show_file_dialog) 26 | 27 | 28 | func _notification(what: int) -> void: 29 | if what == NOTIFICATION_SCENE_INSTANTIATED: 30 | # Since this is a part of an instantiated scene, these nodes are immediately available. 31 | # This allows us to use them safely before ready. 32 | _file_path_label = %FilePathLabel 33 | _pattern_size_value = %PatternSizeValue 34 | 35 | 36 | # Lifecycle. 37 | 38 | ## Override. 39 | func clear(keep_size: bool = false) -> void: 40 | _current_file = "" 41 | _file_path_label.text = _current_file 42 | 43 | super(keep_size) 44 | 45 | 46 | # Helpers. 47 | 48 | func _show_file_dialog() -> void: 49 | if OS.has_feature("web"): 50 | var import_dialog_web := Controller.get_file_dialog_web() 51 | import_dialog_web.add_filter(".mid") 52 | import_dialog_web.file_selected.connect(_confirm_file_dialog, CONNECT_ONE_SHOT) 53 | 54 | import_dialog_web.popup() 55 | return 56 | 57 | var import_dialog := Controller.get_file_dialog() 58 | import_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE 59 | import_dialog.title = "Select File to Import" 60 | import_dialog.add_filter("*.mid", "MIDI File") 61 | import_dialog.current_file = "" 62 | import_dialog.file_selected.connect(_confirm_file_dialog, CONNECT_ONE_SHOT) 63 | 64 | Controller.show_file_dialog(import_dialog) 65 | 66 | 67 | func _confirm_file_dialog(path: String) -> void: 68 | _current_file = path 69 | _file_path_label.text = _current_file 70 | 71 | 72 | # Config management. 73 | 74 | func get_import_config() -> ImportConfig: 75 | var config := ImportConfig.new() 76 | config.type = ImportType.IMPORT_MIDI 77 | config.path = _current_file 78 | config.pattern_size = _pattern_size_value.value 79 | 80 | return config 81 | 82 | 83 | class ImportConfig extends RefCounted: 84 | var type: ImportType = ImportType.NONE 85 | var path: String = "" 86 | var pattern_size: int = 0 87 | -------------------------------------------------------------------------------- /gui/widgets/popups/InfoPopup.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name InfoPopup extends WindowPopup 9 | 10 | enum ImagePosition { 11 | HIDDEN, 12 | IMAGE_TOP, 13 | IMAGE_LEFT, 14 | } 15 | 16 | var content: String = "": 17 | set = set_content 18 | 19 | var _ref_image: Texture2D = null 20 | var _ref_image_size: Vector2 = Vector2.ZERO 21 | var _ref_image_position: ImagePosition = ImagePosition.HIDDEN 22 | 23 | @onready var _content_label: RichTextLabel = %ContentLabel 24 | @onready var _top_image_node: TextureRect = %TopImage 25 | @onready var _left_image_node: TextureRect = %LeftImage 26 | 27 | 28 | func _ready() -> void: 29 | super() 30 | _update_content() 31 | _update_image() 32 | 33 | 34 | # Lifecycle. 35 | 36 | ## Override. 37 | func clear(keep_size: bool = false) -> void: 38 | content = "" 39 | _ref_image = null 40 | _ref_image_size = Vector2.ZERO 41 | _ref_image_position = ImagePosition.HIDDEN 42 | 43 | if is_node_ready(): 44 | _update_image() 45 | 46 | super(keep_size) 47 | 48 | 49 | # Content. 50 | 51 | ## Override. 52 | func _update_before_popup() -> void: 53 | super() 54 | 55 | if is_node_ready(): 56 | _update_content() 57 | 58 | 59 | func set_content(value: String) -> void: 60 | content = value 61 | _update_content() 62 | 63 | 64 | func _update_content() -> void: 65 | if not is_node_ready(): 66 | return 67 | 68 | _content_label.text = content 69 | 70 | 71 | func add_image(texture: Texture2D, image_size: Vector2, image_position: ImagePosition) -> void: 72 | _ref_image = texture 73 | _ref_image_size = image_size 74 | _ref_image_position = image_position 75 | _update_image() 76 | 77 | 78 | func _update_image() -> void: 79 | if not is_node_ready(): 80 | return 81 | 82 | match _ref_image_position: 83 | ImagePosition.HIDDEN: 84 | _top_image_node.custom_minimum_size = Vector2.ZERO 85 | _top_image_node.visible = false 86 | _left_image_node.custom_minimum_size = Vector2.ZERO 87 | _left_image_node.visible = false 88 | 89 | ImagePosition.IMAGE_TOP: 90 | _top_image_node.texture = _ref_image 91 | 92 | _top_image_node.custom_minimum_size = _ref_image_size 93 | _top_image_node.visible = true 94 | _left_image_node.custom_minimum_size = Vector2.ZERO 95 | _left_image_node.visible = false 96 | 97 | ImagePosition.IMAGE_LEFT: 98 | _left_image_node.texture = _ref_image 99 | 100 | _top_image_node.custom_minimum_size = Vector2.ZERO 101 | _top_image_node.visible = false 102 | _left_image_node.custom_minimum_size = _ref_image_size 103 | _left_image_node.visible = true 104 | -------------------------------------------------------------------------------- /gui/widgets/popups/InfoPopup.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://bt23ijx7dg8iw"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://brt3f2i74x70n" path="res://gui/widgets/popups/WindowPopup.tscn" id="1_p2e4k"] 4 | [ext_resource type="Script" path="res://gui/widgets/popups/InfoPopup.gd" id="2_3hir0"] 5 | [ext_resource type="Script" path="res://gui/widgets/AccentedContentEffect.gd" id="3_spm7g"] 6 | 7 | [sub_resource type="RichTextEffect" id="RichTextEffect_ldctv"] 8 | script = ExtResource("3_spm7g") 9 | 10 | [node name="WindowPopup" instance=ExtResource("1_p2e4k")] 11 | theme_type_variation = &"InfoPopup" 12 | script = ExtResource("2_3hir0") 13 | 14 | [node name="ContentHBox" type="HBoxContainer" parent="Layout/Content" index="0"] 15 | layout_mode = 2 16 | mouse_filter = 2 17 | theme_type_variation = &"InfoPopupContentHBox" 18 | 19 | [node name="LeftImage" type="TextureRect" parent="Layout/Content/ContentHBox" index="0"] 20 | unique_name_in_owner = true 21 | visible = false 22 | layout_mode = 2 23 | size_flags_vertical = 0 24 | expand_mode = 1 25 | stretch_mode = 5 26 | 27 | [node name="ContentBox" type="VBoxContainer" parent="Layout/Content/ContentHBox" index="1"] 28 | layout_mode = 2 29 | size_flags_horizontal = 3 30 | mouse_filter = 2 31 | theme_type_variation = &"InfoPopupContentBox" 32 | 33 | [node name="TopImage" type="TextureRect" parent="Layout/Content/ContentHBox/ContentBox" index="0"] 34 | unique_name_in_owner = true 35 | auto_translate_mode = 1 36 | visible = false 37 | layout_mode = 2 38 | size_flags_horizontal = 4 39 | expand_mode = 1 40 | stretch_mode = 5 41 | 42 | [node name="ContentLabel" type="RichTextLabel" parent="Layout/Content/ContentHBox/ContentBox" index="1"] 43 | unique_name_in_owner = true 44 | layout_mode = 2 45 | size_flags_vertical = 3 46 | mouse_filter = 2 47 | bbcode_enabled = true 48 | custom_effects = [SubResource("RichTextEffect_ldctv")] 49 | 50 | [node name="ButtonBar" type="HBoxContainer" parent="Layout/Content/ContentHBox/ContentBox" index="2"] 51 | unique_name_in_owner = true 52 | visible = false 53 | layout_mode = 2 54 | mouse_filter = 2 55 | theme_type_variation = &"HBoxSpaced" 56 | alignment = 2 57 | -------------------------------------------------------------------------------- /gui/widgets/popups/WindowPopup.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://brt3f2i74x70n"] 2 | 3 | [ext_resource type="Script" path="res://gui/widgets/popups/WindowPopup.gd" id="1_v6ml7"] 4 | [ext_resource type="Texture2D" uid="uid://dqatqvd3ie4cw" path="res://assets/icons/cross.png" id="2_cqnjm"] 5 | 6 | [node name="WindowPopup" type="Control"] 7 | layout_mode = 3 8 | anchors_preset = 15 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | offset_right = -782.0 12 | offset_bottom = -608.0 13 | grow_horizontal = 2 14 | grow_vertical = 2 15 | theme_type_variation = &"WindowPopup" 16 | script = ExtResource("1_v6ml7") 17 | 18 | [node name="Layout" type="VBoxContainer" parent="."] 19 | layout_mode = 1 20 | anchors_preset = 15 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | grow_horizontal = 2 24 | grow_vertical = 2 25 | mouse_filter = 2 26 | 27 | [node name="TitleBar" type="MarginContainer" parent="Layout"] 28 | layout_mode = 2 29 | mouse_filter = 2 30 | theme_type_variation = &"WindowPopupTitleBar" 31 | 32 | [node name="TitleBox" type="HBoxContainer" parent="Layout/TitleBar"] 33 | layout_mode = 2 34 | mouse_filter = 2 35 | 36 | [node name="TitleLabel" type="Label" parent="Layout/TitleBar/TitleBox"] 37 | unique_name_in_owner = true 38 | custom_minimum_size = Vector2(0, 30) 39 | layout_mode = 2 40 | size_flags_horizontal = 3 41 | text = "Information" 42 | vertical_alignment = 1 43 | text_overrun_behavior = 3 44 | 45 | [node name="CloseButton" type="Button" parent="Layout/TitleBar/TitleBox"] 46 | unique_name_in_owner = true 47 | custom_minimum_size = Vector2(30, 30) 48 | layout_mode = 2 49 | size_flags_vertical = 4 50 | theme_type_variation = &"WindowPopupTitleButton" 51 | icon = ExtResource("2_cqnjm") 52 | flat = true 53 | icon_alignment = 1 54 | expand_icon = true 55 | 56 | [node name="Content" type="MarginContainer" parent="Layout"] 57 | unique_name_in_owner = true 58 | layout_mode = 2 59 | size_flags_vertical = 3 60 | mouse_filter = 2 61 | theme_type_variation = &"WindowPopupContent" 62 | -------------------------------------------------------------------------------- /help/advanced_guide_delete_instrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/advanced_guide_delete_instrument.png -------------------------------------------------------------------------------- /help/advanced_guide_delete_instrument.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://r26ibp33l6if" 6 | path="res://.godot/imported/advanced_guide_delete_instrument.png-a12489415220cf7241b478a203574a45.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/advanced_guide_delete_instrument.png" 14 | dest_files=["res://.godot/imported/advanced_guide_delete_instrument.png-a12489415220cf7241b478a203574a45.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/advanced_guide_note_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/advanced_guide_note_selection.png -------------------------------------------------------------------------------- /help/advanced_guide_note_selection.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dlmdhoaldml2c" 6 | path="res://.godot/imported/advanced_guide_note_selection.png-dae03107edcae3f06f18e293af9e0f4d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/advanced_guide_note_selection.png" 14 | dest_files=["res://.godot/imported/advanced_guide_note_selection.png-dae03107edcae3f06f18e293af9e0f4d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/advanced_guide_rec_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/advanced_guide_rec_filter.png -------------------------------------------------------------------------------- /help/advanced_guide_rec_filter.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dotqpsqiueyqi" 6 | path="res://.godot/imported/advanced_guide_rec_filter.png-421461dacb4afa100545792bce04a9c9.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/advanced_guide_rec_filter.png" 14 | dest_files=["res://.godot/imported/advanced_guide_rec_filter.png-421461dacb4afa100545792bce04a9c9.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/basic_guide_longnote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/basic_guide_longnote.png -------------------------------------------------------------------------------- /help/basic_guide_longnote.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dvrr63bss01ek" 6 | path="res://.godot/imported/basic_guide_longnote.png-7c19255552d6be852a2283bb06afcf59.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/basic_guide_longnote.png" 14 | dest_files=["res://.godot/imported/basic_guide_longnote.png-7c19255552d6be852a2283bb06afcf59.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/basic_guide_pattern_drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/basic_guide_pattern_drag.png -------------------------------------------------------------------------------- /help/basic_guide_pattern_drag.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bg8islw0t6v7u" 6 | path="res://.godot/imported/basic_guide_pattern_drag.png-867e93a4824db9adbca6eb66157888ff.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/basic_guide_pattern_drag.png" 14 | dest_files=["res://.godot/imported/basic_guide_pattern_drag.png-867e93a4824db9adbca6eb66157888ff.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/basic_guide_pattern_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/basic_guide_pattern_paint.png -------------------------------------------------------------------------------- /help/basic_guide_pattern_paint.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://sut1q8rtgo5d" 6 | path="res://.godot/imported/basic_guide_pattern_paint.png-fd259e457da7d466366d6688e4467f2f.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/basic_guide_pattern_paint.png" 14 | dest_files=["res://.godot/imported/basic_guide_pattern_paint.png-fd259e457da7d466366d6688e4467f2f.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /help/basic_guide_timeline_span.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/help/basic_guide_timeline_span.png -------------------------------------------------------------------------------- /help/basic_guide_timeline_span.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d3o4jiisr6cfw" 6 | path="res://.godot/imported/basic_guide_timeline_span.png-5be31a613bd94e6ec433b59782921426.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://help/basic_guide_timeline_span.png" 14 | dest_files=["res://.godot/imported/basic_guide_timeline_span.png-5be31a613bd94e6ec433b59782921426.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuriSizov/boscaceoil-blue/4b6b5c6c3f73a663dcce078b260fab4901c7ae82/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cq5bt0qijq3pi" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /io/midi/MidiFile.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | # This class is not used at the moment and serves as a collection of shared constants. 8 | class_name MidiFile extends RefCounted 9 | 10 | enum FileFormat { 11 | SINGLE_TRACK, 12 | MULTI_TRACK, 13 | MULTI_SONG, 14 | } 15 | 16 | const DEFAULT_RESOLUTION := 96 17 | const DRUMKIT_CHANNEL := 9 18 | 19 | const TEMPO_BASE := 60_000_000 20 | 21 | const FILE_HEADER_MARKER := "MThd" 22 | const FILE_TRACK_MARKER := "MTrk" 23 | -------------------------------------------------------------------------------- /objects/DrumkitInstrument.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | ## A specialized implementation of a drumkit instrument. 8 | class_name DrumkitInstrument extends Instrument 9 | 10 | ## Collection of voices, one for each drumkit item. 11 | var voices: Array[SiONVoice] = [] 12 | 13 | 14 | # Voice data. 15 | 16 | func set_voice_data(voice_data: VoiceManager.VoiceData) -> void: 17 | super(voice_data) 18 | type = InstrumentType.INSTRUMENT_DRUMKIT 19 | 20 | voices.clear() 21 | 22 | var drumkit_data := _voice_data as VoiceManager.DrumkitData 23 | for item in drumkit_data.items: 24 | var voice := Controller.voice_manager.get_voice_preset(item.voice_preset) 25 | voices.push_back(voice) 26 | 27 | 28 | func is_note_valid(note: Vector3i) -> bool: 29 | # Drumkits have a limited number of "notes" available, potentially below the normal cap. 30 | return note.x < voices.size() 31 | 32 | 33 | func get_note_value(note: int) -> int: 34 | var drumkit_data := _voice_data as VoiceManager.DrumkitData 35 | return drumkit_data.items[note].note 36 | 37 | 38 | func get_note_voice(note: int) -> SiONVoice: 39 | return voices[note] 40 | 41 | 42 | func get_note_name(note: int) -> String: 43 | var drumkit_data := _voice_data as VoiceManager.DrumkitData 44 | return drumkit_data.items[note].name 45 | 46 | 47 | func get_midi_note(note: int) -> int: 48 | var drumkit_data := _voice_data as VoiceManager.DrumkitData 49 | return drumkit_data.items[note].midi_note 50 | 51 | 52 | func get_note_from_midi_note(midi_note: int) -> int: 53 | var drumkit_data := _voice_data as VoiceManager.DrumkitData 54 | 55 | var i := 11 # We skip first 11 as they aren't default MIDI drumkit items. 56 | while i < drumkit_data.items.size(): 57 | if drumkit_data.items[i].midi_note == midi_note: 58 | return i 59 | 60 | i += 1 61 | 62 | return 11 # Default to Bass Drum. 63 | 64 | 65 | # Filter state. 66 | 67 | func update_filter() -> void: 68 | for voice in voices: 69 | if voice.velocity != volume: 70 | voice.update_volumes = true 71 | voice.velocity = volume 72 | 73 | if (voice.get_channel_params().filter_cutoff != lp_cutoff || voice.get_channel_params().filter_resonance != lp_resonance): 74 | voice.set_filter_envelope(0, lp_cutoff, lp_resonance) 75 | 76 | 77 | func change_filter_to(cutoff_: int, resonance_: int, volume_: int) -> void: 78 | for voice in voices: 79 | voice.update_volumes = true 80 | voice.velocity = volume_ 81 | voice.set_filter_envelope(0, cutoff_, resonance_) 82 | -------------------------------------------------------------------------------- /objects/HelpGuide.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name HelpGuide extends Resource 9 | 10 | @export var steps: Array[HelpGuideStep] = [] 11 | -------------------------------------------------------------------------------- /objects/HelpGuideStep.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | @tool 8 | class_name HelpGuideStep extends Resource 9 | 10 | @export var title: String = "": 11 | set(value): title = value; changed.emit() 12 | @export_multiline var description: String = "": 13 | set(value): description = value; changed.emit() 14 | 15 | @export var ref_image: Texture2D = null: 16 | set(value): ref_image = value; changed.emit() 17 | @export var ref_image_size: Vector2 = Vector2.ZERO: 18 | set(value): ref_image_size = value; changed.emit() 19 | @export var ref_image_position: InfoPopup.ImagePosition = InfoPopup.ImagePosition.HIDDEN: 20 | set(value): ref_image_position = value; changed.emit() 21 | 22 | @export var navigation_target: Menu.NavigationTarget = Menu.NavigationTarget.KEEP_CURRENT: 23 | set(value): navigation_target = value; changed.emit() 24 | @export var node_target: HelpManager.StepNodeRef = HelpManager.StepNodeRef.NONE: 25 | set(value): node_target = value; changed.emit() 26 | @export var trigger_target: HelpManager.StepTrigger = HelpManager.StepTrigger.NONE: 27 | set(value): trigger_target = value; changed.emit() 28 | 29 | @export var position_anchor: Vector2 = Vector2(0.5, 0.5): 30 | set(value): position_anchor = value; changed.emit() 31 | @export var position_direction: PopupManager.Direction = PopupManager.Direction.BOTTOM_RIGHT: 32 | set(value): position_direction = value; changed.emit() 33 | @export var size: Vector2 = Vector2(320, 240): 34 | set(value): size = value; changed.emit() 35 | -------------------------------------------------------------------------------- /objects/Instrument.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | ## A description of a musical instrument and its configuration. 8 | class_name Instrument extends Resource 9 | 10 | const INSTRUMENT_NUMBER := 16 11 | 12 | enum InstrumentType { 13 | INSTRUMENT_SINGLE, 14 | INSTRUMENT_DRUMKIT, 15 | MAX 16 | } 17 | 18 | const MAX_VOLUME := 256 19 | const MAX_FILTER_CUTOFF := 128 20 | const MAX_FILTER_RESONANCE := 9 21 | 22 | # Metadata. 23 | 24 | ## Type of the instrument, see InstrumentType. 25 | @export var type: int = 0: 26 | set(value): type = ValueValidator.index(value, InstrumentType.MAX) 27 | ## Internal index of the instrument voice. 28 | @export var voice_index: int = 0: 29 | set(value): voice_index = ValueValidator.posizero(value) # Max value is limited by the number of registered voices. 30 | 31 | # Voice data (runtime only). 32 | 33 | ## Reference to the voice data for the instrument. 34 | var _voice_data: VoiceManager.VoiceData = null 35 | 36 | ## Category of the instrument, used for grouping in UI. 37 | var category: String: 38 | get: return _voice_data.category if _voice_data else "[UNKNOWN]" 39 | set(value): pass 40 | ## Name of the instrument, used in UI. 41 | var name: String: 42 | get: return _voice_data.name if _voice_data else "[Unknown]" 43 | set(value): pass 44 | ## Color palette for the instrument, used to color code the UI. 45 | var color_palette: int: 46 | get: return _voice_data.color_palette if _voice_data else ColorPalette.PALETTE_GRAY 47 | set(value): pass 48 | 49 | # Adjustments. 50 | 51 | ## Volume. 52 | @export var volume: int = MAX_VOLUME: 53 | set(value): volume = ValueValidator.range(value, 0, MAX_VOLUME) 54 | ## Low pass filter cutoff. 55 | @export var lp_cutoff: int = MAX_FILTER_CUTOFF: 56 | set(value): lp_cutoff = ValueValidator.range(value, 0, MAX_FILTER_CUTOFF) 57 | ## Low pass filter resonance. 58 | @export var lp_resonance: int = 0: 59 | set(value): lp_resonance = ValueValidator.range(value, 0, MAX_FILTER_RESONANCE) 60 | 61 | 62 | func _init(voice_data: VoiceManager.VoiceData) -> void: 63 | set_voice_data(voice_data) 64 | 65 | 66 | # Voice data. 67 | 68 | func set_voice_data(voice_data: VoiceManager.VoiceData) -> void: 69 | _voice_data = voice_data 70 | voice_index = _voice_data.index 71 | 72 | 73 | func is_note_valid(_note: Vector3i) -> bool: 74 | return true 75 | 76 | 77 | func get_note_value(note: int) -> int: 78 | return note 79 | 80 | 81 | func get_note_voice(_note: int) -> SiONVoice: 82 | return null 83 | 84 | 85 | # Filter state. 86 | 87 | func update_filter() -> void: 88 | pass # No default implementation. 89 | 90 | 91 | func change_filter_to(_cutoff: int, _resonance: int, _volume: int) -> void: 92 | pass # No default implementation. 93 | -------------------------------------------------------------------------------- /objects/SingleVoiceInstrument.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | ## A specialized implementation of a single voice instrument. 8 | class_name SingleVoiceInstrument extends Instrument 9 | 10 | ## Main voice associated with this instrument. 11 | var voice: SiONVoice = SiONVoice.new() 12 | 13 | 14 | # Voice data. 15 | 16 | func set_voice_data(voice_data: VoiceManager.VoiceData) -> void: 17 | super(voice_data) 18 | type = InstrumentType.INSTRUMENT_SINGLE 19 | 20 | voice = Controller.voice_manager.get_voice_preset(_voice_data.voice_preset) 21 | 22 | 23 | func is_note_valid(_note: Vector3i) -> bool: 24 | return true 25 | 26 | 27 | func get_note_value(note: int) -> int: 28 | return note 29 | 30 | 31 | func get_note_voice(_note: int) -> SiONVoice: 32 | return voice 33 | 34 | 35 | # Filter state. 36 | 37 | func update_filter() -> void: 38 | if not voice: 39 | return 40 | 41 | if voice.velocity != volume: 42 | voice.update_volumes = true 43 | voice.velocity = volume 44 | 45 | if (voice.get_channel_params().filter_cutoff != lp_cutoff || voice.get_channel_params().filter_resonance != lp_resonance): 46 | voice.set_filter_envelope(0, lp_cutoff, lp_resonance) 47 | 48 | 49 | func change_filter_to(cutoff_: int, resonance_: int, volume_: int) -> void: 50 | if not voice: 51 | return 52 | 53 | voice.update_volumes = true 54 | voice.velocity = volume_ 55 | voice.set_filter_envelope(0, cutoff_, resonance_) 56 | -------------------------------------------------------------------------------- /utils/FileWrapper.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name FileWrapper extends RefCounted 8 | 9 | var _handler: FileAccess = null 10 | 11 | 12 | func open(path: String, flags: int) -> int: 13 | _handler = FileAccess.open(path, flags) 14 | return FileAccess.get_open_error() 15 | 16 | 17 | func write_text_contents(contents: String) -> int: 18 | if not is_instance_valid(_handler): 19 | return ERR_FILE_CANT_WRITE 20 | 21 | _handler.store_string(contents) 22 | return _handler.get_error() 23 | 24 | 25 | func write_buffer_contents(contents: PackedByteArray) -> int: 26 | if not is_instance_valid(_handler): 27 | return ERR_FILE_CANT_WRITE 28 | 29 | _handler.store_buffer(contents) 30 | return _handler.get_error() 31 | 32 | 33 | func finalize_write() -> int: 34 | # On web we must prompt the user to download the file. 35 | if OS.has_feature("web"): 36 | # FIXME: For some reason this doesn't work, the length is read correctly, but the buffer is empty. Engine bug? 37 | #_handler.seek(0) 38 | #_handler.get_buffer(_handler.get_length()) 39 | 40 | # Unless we explicitly close the file or seek it to 0, the following call 41 | # returns an empty buffer. Another engine bug? 42 | _handler.close() 43 | 44 | var download_name := _handler.get_path().get_file() 45 | var file_buffer := FileAccess.get_file_as_bytes(_handler.get_path()) 46 | var error := FileAccess.get_open_error() 47 | if error != OK: 48 | return error 49 | 50 | JavaScriptBridge.download_buffer(file_buffer, download_name) 51 | 52 | return OK 53 | -------------------------------------------------------------------------------- /utils/HelpTester.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://cyrjhcnftvyhg"] 2 | 3 | [ext_resource type="Script" path="res://utils/HelpTester.gd" id="1_qy55u"] 4 | [ext_resource type="PackedScene" uid="uid://cf3nug1sfh1ix" path="res://Main.tscn" id="2_ihl17"] 5 | [ext_resource type="PackedScene" uid="uid://bt23ijx7dg8iw" path="res://gui/widgets/popups/InfoPopup.tscn" id="3_j2h0f"] 6 | 7 | [node name="HelpTester" type="Control"] 8 | layout_mode = 3 9 | anchors_preset = 15 10 | anchor_right = 1.0 11 | anchor_bottom = 1.0 12 | grow_horizontal = 2 13 | grow_vertical = 2 14 | script = ExtResource("1_qy55u") 15 | 16 | [node name="Main" parent="." instance=ExtResource("2_ihl17")] 17 | layout_mode = 1 18 | 19 | [node name="Overlay" type="Control" parent="."] 20 | layout_mode = 1 21 | anchors_preset = 15 22 | anchor_right = 1.0 23 | anchor_bottom = 1.0 24 | grow_horizontal = 2 25 | grow_vertical = 2 26 | 27 | [node name="Popup" parent="." instance=ExtResource("3_j2h0f")] 28 | visible = false 29 | custom_minimum_size = Vector2(320, 240) 30 | layout_mode = 1 31 | anchors_preset = -1 32 | anchor_left = 0.5 33 | anchor_top = 0.5 34 | anchor_right = 0.5 35 | anchor_bottom = 0.5 36 | offset_right = 0.0 37 | offset_bottom = 0.0 38 | grow_horizontal = 1 39 | grow_vertical = 1 40 | title = "" 41 | -------------------------------------------------------------------------------- /utils/ValueValidator.gd: -------------------------------------------------------------------------------- 1 | ################################################### 2 | # Part of Bosca Ceoil Blue # 3 | # Copyright (c) 2025 Yuri Sizov and contributors # 4 | # Provided under MIT # 5 | ################################################### 6 | 7 | class_name ValueValidator extends Object 8 | 9 | static func positive(value: int, message: String = "") -> int: 10 | var clean := maxi(value, 1) 11 | if clean != value: 12 | if message.is_empty(): 13 | printerr("Invalid value: Expected a positive value, got %d instead." % [ value ]) 14 | else: 15 | printerr(message) 16 | 17 | return clean 18 | 19 | static func posizero(value: int, message: String = "") -> int: 20 | var clean := maxi(value, 0) 21 | if clean != value: 22 | if message.is_empty(): 23 | printerr("Invalid value: Expected a positive value or zero, got %d instead." % [ value ]) 24 | else: 25 | printerr(message) 26 | 27 | return clean 28 | 29 | static func range(value: int, min_value: int, max_value: int, message: String = "") -> int: 30 | var clean := clampi(value, min_value, max_value) 31 | if clean != value: 32 | if message.is_empty(): 33 | printerr("Invalid value: Expected value in range from %d to %d, got %d instead." % [ min_value, max_value, value ]) 34 | else: 35 | printerr(message) 36 | 37 | return clean 38 | 39 | static func index(value: int, size: int, message: String = "") -> int: 40 | var clean := clampi(value, 0, size - 1) 41 | if clean != value: 42 | if message.is_empty(): 43 | printerr("Invalid value: Expected an index value between 0 and %d, got %d instead." % [ size - 1, value ]) 44 | else: 45 | printerr(message) 46 | 47 | return clean 48 | --------------------------------------------------------------------------------