From 384b8552cf7934abfd0315f5287dbca313f3d3e4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 5 Feb 2026 23:03:19 +0100 Subject: [PATCH] Improve Heavy console with ANSI colour, font weight and command logging --- CMakeLists.txt | 1 + Libraries/JUCE | 2 +- Resources/Fonts/RobotoMono-Bold.ttf | Bin 0 -> 87696 bytes Resources/Scripts/package_resources.py | 1 + Source/Constants.h | 486 ++++++++-------- Source/Heavy/CppExporter.h | 3 +- Source/Heavy/DPFExporter.h | 29 +- Source/Heavy/DaisyExporter.h | 38 +- Source/Heavy/ExporterBase.h | 78 ++- Source/Heavy/ExportingProgressView.h | 536 +++++++++++++++++- Source/Heavy/HeavyExportDialog.cpp | 6 +- Source/Heavy/OWLExporter.h | 20 +- Source/Heavy/PdExporter.h | 21 +- .../{Toolchain.h => ToolchainInstaller.h} | 89 +-- Source/Heavy/WASMExporter.h | 3 +- Source/Pd/Instance.h | 2 +- Source/Utility/Fonts.h | 3 + 17 files changed, 903 insertions(+), 415 deletions(-) create mode 100644 Resources/Fonts/RobotoMono-Bold.ttf rename Source/Heavy/{Toolchain.h => ToolchainInstaller.h} (75%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2207e04a..62d92b023 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,7 @@ file(GLOB plugdata_resources ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/InterVariable.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/InterRegular.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/RobotoMono-Regular.ttf + ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/RobotoMono-Bold.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_large_logo.png ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo.png # Generated resources diff --git a/Libraries/JUCE b/Libraries/JUCE index 77ba47921..39cec94a9 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 77ba479218a73d826a970f4b704571398e29ed41 +Subproject commit 39cec94a9ac14ad07db6a89a7cd143208195fba5 diff --git a/Resources/Fonts/RobotoMono-Bold.ttf b/Resources/Fonts/RobotoMono-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..bef439f3b9a884d4377c193ea52f104e820afc98 GIT binary patch literal 87696 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 Z?uN)pxi0>?VH-^Ty3==XU7v${{}1B`zwrP7 literal 0 HcmV?d00001 diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 187ee5fa0..369556dbe 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -342,6 +342,7 @@ def generate_binary_data(output_dir, file_list): project_root + "/Resources/Fonts/InterVariable.ttf", project_root + "/Resources/Fonts/InterRegular.ttf", project_root + "/Resources/Fonts/RobotoMono-Regular.ttf", + project_root + "/Resources/Fonts/RobotoMono-Bold.ttf", project_root + "/Resources/Icons/plugdata_large_logo.png", project_root + "/Resources/Icons/plugdata_logo.png", "Documentation.bin", diff --git a/Source/Constants.h b/Source/Constants.h index 5d056b13c..b83a873b6 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -10,272 +10,272 @@ #include struct Icons { - inline static String const Open = "b"; - inline static String const Save = "c"; - inline static String const SaveAs = "d"; - inline static String const Undo = "e"; - inline static String const Redo = "f"; - inline static String const Add = "g"; - inline static String const AddObject = ";"; - inline static String const Settings = "h"; - inline static String const Sparkle = "i"; - inline static String const CPU = "j"; - inline static String const Clear = "k"; - inline static String const ClearText = "l"; - inline static String const Lock = "m"; - inline static String const Unlock = "n"; - inline static String const ConnectionStyle = "o"; - inline static String const Power = "p"; - inline static String const Audio = "q"; - inline static String const Search = "r"; - inline static String const Wand = "s"; - inline static String const Pencil = "t"; - inline static String const Grid = "u"; - inline static String const Pin = "v"; - inline static String const Keyboard = "w"; - inline static String const Folder = "x"; - inline static String const OpenedFolder = "y"; - inline static String const File = "z"; - inline static String const New = "z"; - inline static String const AutoScroll = "A"; - inline static String const Restore = "B"; - inline static String const Error = "C"; - inline static String const Message = "D"; - inline static String const Parameters = "E"; - inline static String const Presentation = "F"; - inline static String const Externals = "G"; - inline static String const Refresh = "H"; - inline static String const Up = "I"; - inline static String const Down = "J"; - inline static String const Edit = "K"; - inline static String const ThinDown = "L"; - inline static String const Sine = "M"; - inline static String const Documentation = "N"; - inline static String const AddCircled = "O"; - inline static String const Console = "P"; - inline static String const OpenLink = "Q"; - inline static String const Wrench = "R"; - inline static String const Back = "S"; - inline static String const Forward = "T"; - inline static String const Library = "U"; - inline static String const Menu = "V"; - inline static String const Info = "W"; - inline static String const Warning = "\""; - inline static String const History = "X"; - inline static String const Protection = "Y"; - inline static String const DevTools = "{"; - inline static String const Help = "\\"; - inline static String const Checkmark = "_"; - - inline static String const SavePatch = "Z"; - inline static String const ClosePatch = "["; - inline static String const CloseAllPatches = "]"; - inline static String const Centre = "}"; - inline static String const FitAll = ">"; - inline static String const Eye = "|"; - inline static String const Magnet = "%"; - inline static String const SnapEdges = "#"; - inline static String const SnapCenters = "$"; - inline static String const ExportState = "^"; - inline static String const Trash = "~"; - inline static String const CanvasSettings = "&"; - inline static String const Eyedropper = "@"; - inline static String const HeartFilled = "?"; - inline static String const HeartStroked = ">"; - - inline static String const Reset = "'"; - inline static String const More = "."; - inline static String const MIDI = "`"; - inline static String const PluginMode = "="; - inline static String const CommandInput = "+"; - - inline static String const Reorder = "("; - inline static String const Object = ":"; - inline static String const ObjectMulti = CharPointer_UTF8("\xc2\xb9"); - - inline static String const List = "!"; - inline static String const Graph = "<"; - - inline static String const Heart = ","; - inline static String const Download = "-"; - - inline static String const Copy = "0"; - inline static String const Paste = "1"; - inline static String const Duplicate = "2"; - inline static String const Cut = "3"; - - inline static String const Storage = CharPointer_UTF8("\xc3\x90"); - inline static String const Money = CharPointer_UTF8("\xc3\x91"); - inline static String const Time = CharPointer_UTF8("\xc3\x92"); - inline static String const Store = CharPointer_UTF8("\xc3\x8f"); - inline static String const PanelExpand = CharPointer_UTF8("\xc3\x8d"); - inline static String const PanelContract = CharPointer_UTF8("\xc3\x8c"); - inline static String const ItemGrid = " "; - - inline static String const AlignLeft = "4"; - inline static String const AlignRight = "5"; - inline static String const AlignHCentre = "6"; - inline static String const AlignHDistribute = "/"; - inline static String const AlignTop = "7"; - inline static String const AlignBottom = "8"; - inline static String const AlignVCentre = "9"; - inline static String const AlignVDistribute = "*"; - - inline static String const Home = CharPointer_UTF8("\xc3\x8e"); - - inline static String const ShowIndex = CharPointer_UTF8("\xc2\xbA"); - inline static String const ShowXY = CharPointer_UTF8("\xc2\xbb"); + static inline String const Open = "b"; + static inline String const Save = "c"; + static inline String const SaveAs = "d"; + static inline String const Undo = "e"; + static inline String const Redo = "f"; + static inline String const Add = "g"; + static inline String const AddObject = ";"; + static inline String const Settings = "h"; + static inline String const Sparkle = "i"; + static inline String const CPU = "j"; + static inline String const Clear = "k"; + static inline String const ClearText = "l"; + static inline String const Lock = "m"; + static inline String const Unlock = "n"; + static inline String const ConnectionStyle = "o"; + static inline String const Power = "p"; + static inline String const Audio = "q"; + static inline String const Search = "r"; + static inline String const Wand = "s"; + static inline String const Pencil = "t"; + static inline String const Grid = "u"; + static inline String const Pin = "v"; + static inline String const Keyboard = "w"; + static inline String const Folder = "x"; + static inline String const OpenedFolder = "y"; + static inline String const File = "z"; + static inline String const New = "z"; + static inline String const AutoScroll = "A"; + static inline String const Restore = "B"; + static inline String const Error = "C"; + static inline String const Message = "D"; + static inline String const Parameters = "E"; + static inline String const Presentation = "F"; + static inline String const Externals = "G"; + static inline String const Refresh = "H"; + static inline String const Up = "I"; + static inline String const Down = "J"; + static inline String const Edit = "K"; + static inline String const ThinDown = "L"; + static inline String const Sine = "M"; + static inline String const Documentation = "N"; + static inline String const AddCircled = "O"; + static inline String const Console = "P"; + static inline String const OpenLink = "Q"; + static inline String const Wrench = "R"; + static inline String const Back = "S"; + static inline String const Forward = "T"; + static inline String const Library = "U"; + static inline String const Menu = "V"; + static inline String const Info = "W"; + static inline String const Warning = "\""; + static inline String const History = "X"; + static inline String const Protection = "Y"; + static inline String const DevTools = "{"; + static inline String const Help = "\\"; + static inline String const Checkmark = "_"; + + static inline String const SavePatch = "Z"; + static inline String const ClosePatch = "["; + static inline String const CloseAllPatches = "]"; + static inline String const Centre = "}"; + static inline String const FitAll = ">"; + static inline String const Eye = "|"; + static inline String const Magnet = "%"; + static inline String const SnapEdges = "#"; + static inline String const SnapCenters = "$"; + static inline String const ExportState = "^"; + static inline String const Trash = "~"; + static inline String const CanvasSettings = "&"; + static inline String const Eyedropper = "@"; + static inline String const HeartFilled = "?"; + static inline String const HeartStroked = ">"; + + static inline String const Reset = "'"; + static inline String const More = "."; + static inline String const MIDI = "`"; + static inline String const PluginMode = "="; + static inline String const CommandInput = "+"; + + static inline String const Reorder = "("; + static inline String const Object = ":"; + static inline String const ObjectMulti = CharPointer_UTF8("\xc2\xb9"); + + static inline String const List = "!"; + static inline String const Graph = "<"; + + static inline String const Heart = ","; + static inline String const Download = "-"; + + static inline String const Copy = "0"; + static inline String const Paste = "1"; + static inline String const Duplicate = "2"; + static inline String const Cut = "3"; + + static inline String const Storage = CharPointer_UTF8("\xc3\x90"); + static inline String const Money = CharPointer_UTF8("\xc3\x91"); + static inline String const Time = CharPointer_UTF8("\xc3\x92"); + static inline String const Store = CharPointer_UTF8("\xc3\x8f"); + static inline String const PanelExpand = CharPointer_UTF8("\xc3\x8d"); + static inline String const PanelContract = CharPointer_UTF8("\xc3\x8c"); + static inline String const ItemGrid = " "; + + static inline String const AlignLeft = "4"; + static inline String const AlignRight = "5"; + static inline String const AlignHCentre = "6"; + static inline String const AlignHDistribute = "/"; + static inline String const AlignTop = "7"; + static inline String const AlignBottom = "8"; + static inline String const AlignVCentre = "9"; + static inline String const AlignVDistribute = "*"; + + static inline String const Home = CharPointer_UTF8("\xc3\x8e"); + + static inline String const ShowIndex = CharPointer_UTF8("\xc2\xbA"); + static inline String const ShowXY = CharPointer_UTF8("\xc2\xbb"); // ================== OBJECT ICONS ================== // generic - inline static String const GlyphGenericSignal = CharPointer_UTF8("\xc3\x80"); - inline static String const GlyphGeneric = CharPointer_UTF8("\xc3\x81"); + static inline String const GlyphGenericSignal = CharPointer_UTF8("\xc3\x80"); + static inline String const GlyphGeneric = CharPointer_UTF8("\xc3\x81"); // default - inline static String const GlyphEmptyObject = CharPointer_UTF8("\xc3\x82"); - inline static String const GlyphMessage = CharPointer_UTF8("\xc3\x84"); - inline static String const GlyphFloatBox = CharPointer_UTF8("\xc3\x83"); - inline static String const GlyphSymbolBox = CharPointer_UTF8("\xc3\x85"); - inline static String const GlyphListBox = CharPointer_UTF8("\xc3\x86"); - inline static String const GlyphComment = CharPointer_UTF8("\xc3\x87"); + static inline String const GlyphEmptyObject = CharPointer_UTF8("\xc3\x82"); + static inline String const GlyphMessage = CharPointer_UTF8("\xc3\x84"); + static inline String const GlyphFloatBox = CharPointer_UTF8("\xc3\x83"); + static inline String const GlyphSymbolBox = CharPointer_UTF8("\xc3\x85"); + static inline String const GlyphListBox = CharPointer_UTF8("\xc3\x86"); + static inline String const GlyphComment = CharPointer_UTF8("\xc3\x87"); // ui - inline static String const GlyphBang = CharPointer_UTF8("\xc2\xa1"); - inline static String const GlyphToggle = CharPointer_UTF8("\xc2\xa2"); - inline static String const GlyphButton = CharPointer_UTF8("\xc2\xa3"); - inline static String const GlyphKnob = CharPointer_UTF8("\xc2\xa4"); - inline static String const GlyphNumber = CharPointer_UTF8("\xc2\xa5"); - inline static String const GlyphHSlider = CharPointer_UTF8("\xc2\xa8"); - inline static String const GlyphVSlider = CharPointer_UTF8("\xc2\xa9"); - inline static String const GlyphHRadio = CharPointer_UTF8("\xc2\xa6"); - inline static String const GlyphVRadio = CharPointer_UTF8("\xc2\xa7"); - inline static String const GlyphCanvas = CharPointer_UTF8("\xc2\xaa"); - inline static String const GlyphKeyboard = CharPointer_UTF8("\xc2\xab"); - inline static String const GlyphVUMeter = CharPointer_UTF8("\xc2\xac"); - inline static String const GlyphArray = CharPointer_UTF8("\xc2\xae"); - inline static String const GlyphGOP = CharPointer_UTF8("\xc2\xaf"); - inline static String const GlyphOscilloscope = CharPointer_UTF8("\xc2\xb0"); - inline static String const GlyphFunction = CharPointer_UTF8("\xc2\xb1"); - inline static String const GlyphMessbox = CharPointer_UTF8("\xc2\xb5"); - inline static String const GlyphBicoeff = CharPointer_UTF8("\xc2\xb3"); + static inline String const GlyphBang = CharPointer_UTF8("\xc2\xa1"); + static inline String const GlyphToggle = CharPointer_UTF8("\xc2\xa2"); + static inline String const GlyphButton = CharPointer_UTF8("\xc2\xa3"); + static inline String const GlyphKnob = CharPointer_UTF8("\xc2\xa4"); + static inline String const GlyphNumber = CharPointer_UTF8("\xc2\xa5"); + static inline String const GlyphHSlider = CharPointer_UTF8("\xc2\xa8"); + static inline String const GlyphVSlider = CharPointer_UTF8("\xc2\xa9"); + static inline String const GlyphHRadio = CharPointer_UTF8("\xc2\xa6"); + static inline String const GlyphVRadio = CharPointer_UTF8("\xc2\xa7"); + static inline String const GlyphCanvas = CharPointer_UTF8("\xc2\xaa"); + static inline String const GlyphKeyboard = CharPointer_UTF8("\xc2\xab"); + static inline String const GlyphVUMeter = CharPointer_UTF8("\xc2\xac"); + static inline String const GlyphArray = CharPointer_UTF8("\xc2\xae"); + static inline String const GlyphGOP = CharPointer_UTF8("\xc2\xaf"); + static inline String const GlyphOscilloscope = CharPointer_UTF8("\xc2\xb0"); + static inline String const GlyphFunction = CharPointer_UTF8("\xc2\xb1"); + static inline String const GlyphMessbox = CharPointer_UTF8("\xc2\xb5"); + static inline String const GlyphBicoeff = CharPointer_UTF8("\xc2\xb3"); // general - inline static String const GlyphMetro = CharPointer_UTF8("\xc3\xa4"); - inline static String const GlyphCounter = CharPointer_UTF8("\xc3\xa6"); - inline static String const GlyphSelect = CharPointer_UTF8("\xc3\xa7"); - inline static String const GlyphRoute = CharPointer_UTF8("\xc3\xa8"); - inline static String const GlyphExpr = CharPointer_UTF8("\xc3\xb5"); - inline static String const GlyphLoadbang = CharPointer_UTF8("\xc3\xa9"); - inline static String const GlyphPack = CharPointer_UTF8("\xc3\xaa"); - inline static String const GlyphUnpack = CharPointer_UTF8("\xc3\xab"); - inline static String const GlyphPrint = CharPointer_UTF8("\xc3\xac"); - inline static String const GlyphNetsend = CharPointer_UTF8("\xc3\xae"); - inline static String const GlyphNetreceive = CharPointer_UTF8("\xc3\xad"); - inline static String const GlyphOSCsend = CharPointer_UTF8("\xc4\xb5"); - inline static String const GlyphOSCreceive = CharPointer_UTF8("\xc4\xb4"); - inline static String const GlyphTimer = CharPointer_UTF8("\xc3\xb6"); - inline static String const GlyphDelay = CharPointer_UTF8("\xc3\xb7"); - inline static String const GlyphTrigger = CharPointer_UTF8("\xc3\xb1"); - inline static String const GlyphMoses = CharPointer_UTF8("\xc3\xb2"); - inline static String const GlyphSpigot = CharPointer_UTF8("\xc3\xb3"); - inline static String const GlyphBondo = CharPointer_UTF8("\xc3\xb4"); - inline static String const GlyphSfz = CharPointer_UTF8("\xc3\xb8"); + static inline String const GlyphMetro = CharPointer_UTF8("\xc3\xa4"); + static inline String const GlyphCounter = CharPointer_UTF8("\xc3\xa6"); + static inline String const GlyphSelect = CharPointer_UTF8("\xc3\xa7"); + static inline String const GlyphRoute = CharPointer_UTF8("\xc3\xa8"); + static inline String const GlyphExpr = CharPointer_UTF8("\xc3\xb5"); + static inline String const GlyphLoadbang = CharPointer_UTF8("\xc3\xa9"); + static inline String const GlyphPack = CharPointer_UTF8("\xc3\xaa"); + static inline String const GlyphUnpack = CharPointer_UTF8("\xc3\xab"); + static inline String const GlyphPrint = CharPointer_UTF8("\xc3\xac"); + static inline String const GlyphNetsend = CharPointer_UTF8("\xc3\xae"); + static inline String const GlyphNetreceive = CharPointer_UTF8("\xc3\xad"); + static inline String const GlyphOSCsend = CharPointer_UTF8("\xc4\xb5"); + static inline String const GlyphOSCreceive = CharPointer_UTF8("\xc4\xb4"); + static inline String const GlyphTimer = CharPointer_UTF8("\xc3\xb6"); + static inline String const GlyphDelay = CharPointer_UTF8("\xc3\xb7"); + static inline String const GlyphTrigger = CharPointer_UTF8("\xc3\xb1"); + static inline String const GlyphMoses = CharPointer_UTF8("\xc3\xb2"); + static inline String const GlyphSpigot = CharPointer_UTF8("\xc3\xb3"); + static inline String const GlyphBondo = CharPointer_UTF8("\xc3\xb4"); + static inline String const GlyphSfz = CharPointer_UTF8("\xc3\xb8"); // MIDI - inline static String const GlyphMidiIn = CharPointer_UTF8("\xc4\x87"); - inline static String const GlyphMidiOut = CharPointer_UTF8("\xc4\x88"); - inline static String const GlyphNoteIn = CharPointer_UTF8("\xc4\x89"); - inline static String const GlyphNoteOut = CharPointer_UTF8("\xc4\x8a"); - inline static String const GlyphCtlIn = CharPointer_UTF8("\xc4\x8b"); - inline static String const GlyphCtlOut = CharPointer_UTF8("\xc4\x8c"); - inline static String const GlyphPgmIn = CharPointer_UTF8("\xc4\x8d"); - inline static String const GlyphPgmOut = CharPointer_UTF8("\xc4\x8e"); - inline static String const GlyphSysexIn = CharPointer_UTF8("\xc4\x8f"); - inline static String const GlyphSysexOut = CharPointer_UTF8("\xc4\x90"); - inline static String const GlyphMtof = CharPointer_UTF8("\xc4\x91"); - inline static String const GlyphFtom = CharPointer_UTF8("\xc4\x92"); - inline static String const GlyphAutotune = CharPointer_UTF8("\xc4\x93"); + static inline String const GlyphMidiIn = CharPointer_UTF8("\xc4\x87"); + static inline String const GlyphMidiOut = CharPointer_UTF8("\xc4\x88"); + static inline String const GlyphNoteIn = CharPointer_UTF8("\xc4\x89"); + static inline String const GlyphNoteOut = CharPointer_UTF8("\xc4\x8a"); + static inline String const GlyphCtlIn = CharPointer_UTF8("\xc4\x8b"); + static inline String const GlyphCtlOut = CharPointer_UTF8("\xc4\x8c"); + static inline String const GlyphPgmIn = CharPointer_UTF8("\xc4\x8d"); + static inline String const GlyphPgmOut = CharPointer_UTF8("\xc4\x8e"); + static inline String const GlyphSysexIn = CharPointer_UTF8("\xc4\x8f"); + static inline String const GlyphSysexOut = CharPointer_UTF8("\xc4\x90"); + static inline String const GlyphMtof = CharPointer_UTF8("\xc4\x91"); + static inline String const GlyphFtom = CharPointer_UTF8("\xc4\x92"); + static inline String const GlyphAutotune = CharPointer_UTF8("\xc4\x93"); // Multi~ - inline static String const GlyphMultiSnake = CharPointer_UTF8("\xc4\xbf"); - inline static String const GlyphMultiGet = CharPointer_UTF8("\xc5\x82"); - inline static String const GlyphMultiPick = CharPointer_UTF8("\xc5\x81"); - inline static String const GlyphMultiSig = CharPointer_UTF8("\xc5\x83"); - inline static String const GlyphMultiMerge = CharPointer_UTF8("\xc5\x84"); - inline static String const GlyphMultiUnmerge = CharPointer_UTF8("\xc5\x85"); + static inline String const GlyphMultiSnake = CharPointer_UTF8("\xc4\xbf"); + static inline String const GlyphMultiGet = CharPointer_UTF8("\xc5\x82"); + static inline String const GlyphMultiPick = CharPointer_UTF8("\xc5\x81"); + static inline String const GlyphMultiSig = CharPointer_UTF8("\xc5\x83"); + static inline String const GlyphMultiMerge = CharPointer_UTF8("\xc5\x84"); + static inline String const GlyphMultiUnmerge = CharPointer_UTF8("\xc5\x85"); // IO~ - inline static String const GlyphAdc = CharPointer_UTF8("\xc4\xaa"); - inline static String const GlyphDac = CharPointer_UTF8("\xc4\xab"); - inline static String const GlyphOut = CharPointer_UTF8("\xc4\xac"); - inline static String const GlyphBlocksize = CharPointer_UTF8("\xc4\xad"); - inline static String const GlyphSamplerate = CharPointer_UTF8("\xc4\xae"); - inline static String const GlyphSetDsp = CharPointer_UTF8("\xc4\xaf"); - inline static String const GlyphSend = CharPointer_UTF8("\xc4\xb0"); - inline static String const GlyphReceive = CharPointer_UTF8("\xc4\xb1"); - inline static String const GlyphSignalSend = CharPointer_UTF8("\xc4\xb2"); - inline static String const GlyphSignalReceive = CharPointer_UTF8("\xc4\xb3"); + static inline String const GlyphAdc = CharPointer_UTF8("\xc4\xaa"); + static inline String const GlyphDac = CharPointer_UTF8("\xc4\xab"); + static inline String const GlyphOut = CharPointer_UTF8("\xc4\xac"); + static inline String const GlyphBlocksize = CharPointer_UTF8("\xc4\xad"); + static inline String const GlyphSamplerate = CharPointer_UTF8("\xc4\xae"); + static inline String const GlyphSetDsp = CharPointer_UTF8("\xc4\xaf"); + static inline String const GlyphSend = CharPointer_UTF8("\xc4\xb0"); + static inline String const GlyphReceive = CharPointer_UTF8("\xc4\xb1"); + static inline String const GlyphSignalSend = CharPointer_UTF8("\xc4\xb2"); + static inline String const GlyphSignalReceive = CharPointer_UTF8("\xc4\xb3"); // OSC~ - inline static String const GlyphOsc = CharPointer_UTF8("\xc5\x8d"); - inline static String const GlyphPhasor = CharPointer_UTF8("\xc5\x8e"); - inline static String const GlyphSaw = CharPointer_UTF8("\xc5\x8f"); - inline static String const GlyphSaw2 = CharPointer_UTF8("\xc5\x90"); - inline static String const GlyphSquare = CharPointer_UTF8("\xc5\x91"); - inline static String const GlyphTriangle = CharPointer_UTF8("\xc5\x92"); - inline static String const GlyphImp = CharPointer_UTF8("\xc5\x93"); - inline static String const GlyphImp2 = CharPointer_UTF8("\xc5\x94"); - inline static String const GlyphWavetable = CharPointer_UTF8("\xc5\x95"); - inline static String const GlyphPlaits = CharPointer_UTF8("\xc5\x96"); - - inline static String const GlyphOscBL = CharPointer_UTF8("\xc5\x97"); - inline static String const GlyphSawBL = CharPointer_UTF8("\xc5\x98"); - inline static String const GlyphSawBL2 = CharPointer_UTF8("\xc5\x99"); - inline static String const GlyphSquareBL = CharPointer_UTF8("\xc5\x9a"); - inline static String const GlyphTriBL = CharPointer_UTF8("\xc5\x9b"); - inline static String const GlyphImpBL = CharPointer_UTF8("\xc5\x9c"); - inline static String const GlyphImpBL2 = CharPointer_UTF8("\xc5\x9d"); - inline static String const GlyphWavetableBL = CharPointer_UTF8("\xc5\x9e"); - inline static String const GlyphLFORamp = CharPointer_UTF8("\xc5\x8f"); - inline static String const GlyphLFOSaw = CharPointer_UTF8("\xc5\x9f"); - inline static String const GlyphLFOSquare = CharPointer_UTF8("\xc5\xa0"); - inline static String const GlyphPulse = CharPointer_UTF8("\xc5\xa1"); - inline static String const GlyphPinknoise = CharPointer_UTF8("\xc5\xa2"); + static inline String const GlyphOsc = CharPointer_UTF8("\xc5\x8d"); + static inline String const GlyphPhasor = CharPointer_UTF8("\xc5\x8e"); + static inline String const GlyphSaw = CharPointer_UTF8("\xc5\x8f"); + static inline String const GlyphSaw2 = CharPointer_UTF8("\xc5\x90"); + static inline String const GlyphSquare = CharPointer_UTF8("\xc5\x91"); + static inline String const GlyphTriangle = CharPointer_UTF8("\xc5\x92"); + static inline String const GlyphImp = CharPointer_UTF8("\xc5\x93"); + static inline String const GlyphImp2 = CharPointer_UTF8("\xc5\x94"); + static inline String const GlyphWavetable = CharPointer_UTF8("\xc5\x95"); + static inline String const GlyphPlaits = CharPointer_UTF8("\xc5\x96"); + + static inline String const GlyphOscBL = CharPointer_UTF8("\xc5\x97"); + static inline String const GlyphSawBL = CharPointer_UTF8("\xc5\x98"); + static inline String const GlyphSawBL2 = CharPointer_UTF8("\xc5\x99"); + static inline String const GlyphSquareBL = CharPointer_UTF8("\xc5\x9a"); + static inline String const GlyphTriBL = CharPointer_UTF8("\xc5\x9b"); + static inline String const GlyphImpBL = CharPointer_UTF8("\xc5\x9c"); + static inline String const GlyphImpBL2 = CharPointer_UTF8("\xc5\x9d"); + static inline String const GlyphWavetableBL = CharPointer_UTF8("\xc5\x9e"); + static inline String const GlyphLFORamp = CharPointer_UTF8("\xc5\x8f"); + static inline String const GlyphLFOSaw = CharPointer_UTF8("\xc5\x9f"); + static inline String const GlyphLFOSquare = CharPointer_UTF8("\xc5\xa0"); + static inline String const GlyphPulse = CharPointer_UTF8("\xc5\xa1"); + static inline String const GlyphPinknoise = CharPointer_UTF8("\xc5\xa2"); // effects~ - inline static String const GlyphCrusher = CharPointer_UTF8("\xc6\x99"); - inline static String const GlyphDelayEffect = CharPointer_UTF8("\xc6\x9a"); - inline static String const GlyphDrive = CharPointer_UTF8("\xc6\x9b"); - inline static String const GlyphFlanger = CharPointer_UTF8("\xc6\x9c"); - inline static String const GlyphReverb = CharPointer_UTF8("\xc6\x9d"); - inline static String const GlyphFreeze = CharPointer_UTF8("\xc6\x9e"); - inline static String const GlyphRingmod = CharPointer_UTF8("\xc6\xa2"); - inline static String const GlyphSVFilter = CharPointer_UTF8("\xc6\xa6"); - inline static String const GlyphClip = CharPointer_UTF8("\xc6\xa3"); - inline static String const GlyphFold = CharPointer_UTF8("\xc6\xa4"); - inline static String const GlyphWrap = CharPointer_UTF8("\xc6\xa5"); - inline static String const GlyphCombRev = CharPointer_UTF8("\xc6\x9f"); - inline static String const GlyphComp = CharPointer_UTF8("\xc6\xa0"); - inline static String const GlyphBallance = CharPointer_UTF8("\xc6\xa7"); - inline static String const GlyphPan = CharPointer_UTF8("\xc6\xa8"); + static inline String const GlyphCrusher = CharPointer_UTF8("\xc6\x99"); + static inline String const GlyphDelayEffect = CharPointer_UTF8("\xc6\x9a"); + static inline String const GlyphDrive = CharPointer_UTF8("\xc6\x9b"); + static inline String const GlyphFlanger = CharPointer_UTF8("\xc6\x9c"); + static inline String const GlyphReverb = CharPointer_UTF8("\xc6\x9d"); + static inline String const GlyphFreeze = CharPointer_UTF8("\xc6\x9e"); + static inline String const GlyphRingmod = CharPointer_UTF8("\xc6\xa2"); + static inline String const GlyphSVFilter = CharPointer_UTF8("\xc6\xa6"); + static inline String const GlyphClip = CharPointer_UTF8("\xc6\xa3"); + static inline String const GlyphFold = CharPointer_UTF8("\xc6\xa4"); + static inline String const GlyphWrap = CharPointer_UTF8("\xc6\xa5"); + static inline String const GlyphCombRev = CharPointer_UTF8("\xc6\x9f"); + static inline String const GlyphComp = CharPointer_UTF8("\xc6\xa0"); + static inline String const GlyphBallance = CharPointer_UTF8("\xc6\xa7"); + static inline String const GlyphPan = CharPointer_UTF8("\xc6\xa8"); // filters~ - inline static String const GlyphLowpass = CharPointer_UTF8("\xc7\x8b"); - inline static String const GlyphHighpass = CharPointer_UTF8("\xc7\x8c"); - inline static String const GlyphBandpass = CharPointer_UTF8("\xc7\x8d"); - inline static String const GlyphNotch = CharPointer_UTF8("\xc7\x8e"); - inline static String const GlyphRezLowpass = CharPointer_UTF8("\xc7\x8f"); - inline static String const GlyphRezHighpass = CharPointer_UTF8("\xc7\x90"); - inline static String const GlyphLowShelf = CharPointer_UTF8("\xc7\x91"); - inline static String const GlyphHighShelf = CharPointer_UTF8("\xc7\x92"); - inline static String const GlyphAllPass = CharPointer_UTF8("\xc7\x93"); - inline static String const GlyphFreqShift = CharPointer_UTF8("\xc6\x9a"); + static inline String const GlyphLowpass = CharPointer_UTF8("\xc7\x8b"); + static inline String const GlyphHighpass = CharPointer_UTF8("\xc7\x8c"); + static inline String const GlyphBandpass = CharPointer_UTF8("\xc7\x8d"); + static inline String const GlyphNotch = CharPointer_UTF8("\xc7\x8e"); + static inline String const GlyphRezLowpass = CharPointer_UTF8("\xc7\x8f"); + static inline String const GlyphRezHighpass = CharPointer_UTF8("\xc7\x90"); + static inline String const GlyphLowShelf = CharPointer_UTF8("\xc7\x91"); + static inline String const GlyphHighShelf = CharPointer_UTF8("\xc7\x92"); + static inline String const GlyphAllPass = CharPointer_UTF8("\xc7\x93"); + static inline String const GlyphFreqShift = CharPointer_UTF8("\xc6\x9a"); // plugdata icon with three styles - inline static String const PlugdataIconStandard = CharPointer_UTF8("\xc2\xbc"); - inline static String const PlugdataIconFilled = CharPointer_UTF8("\xc2\xbd"); - inline static String const PlugdataIconSilhouette = CharPointer_UTF8("\xc2\xbe"); + static inline String const PlugdataIconStandard = CharPointer_UTF8("\xc2\xbc"); + static inline String const PlugdataIconFilled = CharPointer_UTF8("\xc2\xbd"); + static inline String const PlugdataIconSilhouette = CharPointer_UTF8("\xc2\xbe"); }; enum PlugDataColour { @@ -430,7 +430,7 @@ struct Corners { static constexpr float largeCornerRadius = 8.0f; static constexpr float defaultCornerRadius = 5.0f; static constexpr float resizeHanleCornerRadius = 2.75f; - inline static float objectCornerRadius = 2.75f; + static inline float objectCornerRadius = 2.75f; }; enum Overlay { diff --git a/Source/Heavy/CppExporter.h b/Source/Heavy/CppExporter.h index 5e663ee0c..ca2ac0aa2 100644 --- a/Source/Heavy/CppExporter.h +++ b/Source/Heavy/CppExporter.h @@ -55,8 +55,7 @@ class CppExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); diff --git a/Source/Heavy/DPFExporter.h b/Source/Heavy/DPFExporter.h index e626dce7c..1516b3bfb 100644 --- a/Source/Heavy/DPFExporter.h +++ b/Source/Heavy/DPFExporter.h @@ -220,8 +220,7 @@ class DPFExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -234,11 +233,11 @@ class DPFExporter final : public ExporterBase { outputFile.getChildFile("hv").deleteRecursively(); outputFile.getChildFile("c").deleteRecursively(); - auto const DPF = Toolchain::dir.getChildFile("lib").getChildFile("dpf"); + auto const DPF = toolchainDir.getChildFile("lib").getChildFile("dpf"); DPF.copyDirectoryTo(outputFile.getChildFile("dpf")); if (exportType == 2 || exportType == 4) { - auto const DPFGui = Toolchain::dir.getChildFile("lib").getChildFile("dpf-widgets"); + auto const DPFGui = toolchainDir.getChildFile("lib").getChildFile("dpf-widgets"); DPFGui.copyDirectoryTo(outputFile.getChildFile("dpf-widgets")); } @@ -256,29 +255,29 @@ class DPFExporter final : public ExporterBase { outputFile.setAsCurrentWorkingDirectory(); - auto const bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto make = bin.getChildFile("make" + exeSuffix); auto makefile = outputFile.getChildFile("Makefile"); - + #if JUCE_MAC - Toolchain::startShellScript("make -j4 -f " + makefile.getFullPathName(), this); + startShellScript("make -j4 -f " + makefile.getFullPathName()); #elif JUCE_WINDOWS - auto path = "export PATH=\"$PATH:" + pathToString(Toolchain::dir.getChildFile("bin")) + "\"\n"; - auto cc = "CC=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("gcc.exe")) + " "; - auto cxx = "CXX=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("g++.exe")) + " "; - auto shell = " SHELL=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("bash.exe")).quoted(); - Toolchain::startShellScript(path + cc + cxx + pathToString(make) + " -j4 -f " + pathToString(makefile) + shell, this); + auto path = "export PATH=\"$PATH:" + pathToString(toolchainDir.getChildFile("bin")) + "\"\n"; + auto cc = "CC=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("gcc.exe")) + " "; + auto cxx = "CXX=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("g++.exe")) + " "; + auto shell = " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted(); + startShellScript(path + cc + cxx + pathToString(make) + " -j4 -f " + pathToString(makefile) + shell); #else // Linux or BSD - auto prepareEnvironmentScript = pathToString(Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh")) + "\n"; + auto prepareEnvironmentScript = pathToString(toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh")) + "\n"; auto buildScript = prepareEnvironmentScript + pathToString(make) + " -j4 -f " + pathToString(makefile); // For some reason we need to do this again outputFile.getChildFile("dpf").getChildFile("utils").getChildFile("generate-ttl.sh").setExecutePermission(true); - Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getChildFile("generate-ttl.sh").setExecutePermission(true); + toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getChildFile("generate-ttl.sh").setExecutePermission(true); - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); #endif waitForProcessToFinish(-1); diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index 77377d3dc..d058c0286 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -73,7 +73,7 @@ class DaisyExporter final : public ExporterBase { flashButton.onClick = [this] { auto const tempFolder = File::getSpecialLocation(File::tempDirectory).getChildFile("Heavy-" + Uuid().toString().substring(10)); - Toolchain::deleteTempFileLater(tempFolder); + deleteTempFileLater(tempFolder); startExport(tempFolder); }; @@ -82,10 +82,10 @@ class DaisyExporter final : public ExporterBase { exportingView->monitorProcessOutput(this); exportingView->showState(ExportingProgressView::Flashing); - auto const bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto const make = bin.getChildFile("make" + exeSuffix); auto const& gccPath = bin.getFullPathName(); - auto const sourceDir = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy").getChildFile("core"); + auto const sourceDir = toolchainDir.getChildFile("lib").getChildFile("libdaisy").getChildFile("core"); int const result = flashBootloader(bin, sourceDir, make, gccPath); @@ -218,7 +218,7 @@ class DaisyExporter final : public ExporterBase { + pathToString(make) + " program-boot" + " GCC_PATH=" + gccPath; - Toolchain::startShellScript(bootloaderScript, this); + startShellScript(bootloaderScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -262,7 +262,7 @@ class DaisyExporter final : public ExporterBase { if (board == "custom") { metaDaisy.getDynamicObject()->setProperty("board_file", customBoardDefinition.getFullPathName()); } else if (extra_boards.contains(board)) { - metaDaisy.getDynamicObject()->setProperty("board_file", Toolchain::dir.getChildFile("etc").getChildFile(board + ".json").getFullPathName()); + metaDaisy.getDynamicObject()->setProperty("board_file", toolchainDir.getChildFile("etc").getChildFile(board + ".json").getFullPathName()); } else { metaDaisy.getDynamicObject()->setProperty("board", board); } @@ -301,7 +301,7 @@ class DaisyExporter final : public ExporterBase { } else if (size == 3) { metaDaisy.getDynamicObject()->setProperty( "linker_script", - Toolchain::dir.getChildFile("etc").getChildFile("linkers").getChildFile("sram_linker_sdram.lds").getFullPathName()); + toolchainDir.getChildFile("etc").getChildFile("linkers").getChildFile("sram_linker_sdram.lds").getFullPathName()); metaDaisy.getDynamicObject()->setProperty("bootloader", "BOOT_SRAM"); } else if (size == 4) { metaDaisy.getDynamicObject()->setProperty("linker_script", "../../libdaisy/core/STM32H750IB_qspi.lds"); @@ -309,7 +309,7 @@ class DaisyExporter final : public ExporterBase { } else if (size == 5) { metaDaisy.getDynamicObject()->setProperty( "linker_script", - Toolchain::dir.getChildFile("etc").getChildFile("linkers").getChildFile("qspi_linker_sdram.lds").getFullPathName()); + toolchainDir.getChildFile("etc").getChildFile("linkers").getChildFile("qspi_linker_sdram.lds").getFullPathName()); metaDaisy.getDynamicObject()->setProperty("bootloader", "BOOT_QSPI"); } else if (size == 6) { metaDaisy.getDynamicObject()->setProperty("linker_script", customLinker.getFullPathName()); @@ -333,8 +333,8 @@ class DaisyExporter final : public ExporterBase { } auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); + waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -353,8 +353,8 @@ class DaisyExporter final : public ExporterBase { metaJsonFile.copyFileTo(outputFile.getChildFile("meta.json")); if (compile) { - auto bin = Toolchain::dir.getChildFile("bin"); - auto libDaisy = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy"); + auto bin = toolchainDir.getChildFile("bin"); + auto libDaisy = toolchainDir.getChildFile("lib").getChildFile("libdaisy"); auto make = bin.getChildFile("make" + exeSuffix); auto compiler = bin.getChildFile("arm-none-eabi-gcc" + exeSuffix); @@ -371,17 +371,22 @@ class DaisyExporter final : public ExporterBase { sourceDir.getChildFile("build").createDirectory(); auto const& gccPath = pathToString(bin); + // Bit hacky, but the only way to get colour coding on daisy builds for Windows +#if JUCE_WINDOWS + sourceDir.getChildFile("Makefile").appendText("\nCFLAGS += -fdiagnostics-color=always"); +#endif + auto buildScript = pathToString(make) + " -j4 -f " + pathToString(sourceDir.getChildFile("Makefile")).quoted() #if JUCE_WINDOWS - + " SHELL=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("bash.exe")).quoted() + + " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted() #endif + " GCC_PATH=" + gccPath + " PROJECT_NAME=" + name; - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -416,8 +421,7 @@ class DaisyExporter final : public ExporterBase { String testBootloaderScript = "export PATH=\"" + bin.getFullPathName() + ":$PATH\"\n" + dfuUtil.getFullPathName() + " -l "; - Toolchain runTest; - auto output = runTest.startShellScriptWithOutput(testBootloaderScript); + auto output = startShellScriptWithOutput(testBootloaderScript); if (output.contains("alt=1")) { exportingView->logToConsole("Bootloader not found...\n"); bootloaderExitCode = flashBootloader(bin, sourceDir, make, gccPath); @@ -434,7 +438,7 @@ class DaisyExporter final : public ExporterBase { + " GCC_PATH=" + gccPath + " PROJECT_NAME=" + name; - Toolchain::startShellScript(flashScript, this); + startShellScript(flashScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -456,7 +460,7 @@ class DaisyExporter final : public ExporterBase { } else { auto outputFile = File(outdir); - auto libDaisy = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy"); + auto libDaisy = toolchainDir.getChildFile("lib").getChildFile("libdaisy"); libDaisy.copyDirectoryTo(outputFile.getChildFile("libdaisy")); outputFile.getChildFile("ir").deleteRecursively(); diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 420cbe704..be4f131bb 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -22,13 +22,16 @@ struct ExporterBase : public Component bool blockDialog = false; #if JUCE_WINDOWS - inline static String const exeSuffix = ".exe"; + static inline File const toolchainDir = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("usr"); + static inline String const exeSuffix = ".exe"; #else - inline static String const exeSuffix = ""; + static inline File const toolchainDir = ProjectInfo::appDataDir.getChildFile("Toolchain"); + static inline String const exeSuffix = ""; #endif - inline static File heavyExecutable = Toolchain::dir.getChildFile("bin").getChildFile("Heavy").getChildFile("Heavy" + exeSuffix); - + static inline File heavyExecutable = toolchainDir.getChildFile("bin").getChildFile("Heavy").getChildFile("Heavy" + exeSuffix); + static inline SmallArray tempFilesToDelete; + bool validPatchSelected = false; bool canvasDirty = false; bool isTempFile = false; @@ -45,6 +48,8 @@ struct ExporterBase : public Component Label unsavedLabel = Label("", "Warning: patch has unsaved changes"); PluginEditor* editor; + + ExporterBase(PluginEditor* pluginEditor, ExportingProgressView* exportView) : ThreadPool(1, Thread::osDefaultStackSize, Thread::Priority::highest) @@ -86,7 +91,7 @@ struct ExporterBase : public Component if (auto const* cnv = editor->getCurrentCanvas()) { openedPatchFile = File::createTempFile(".pd"); - Toolchain::deleteTempFileLater(openedPatchFile); + deleteTempFileLater(openedPatchFile); patchFile = cnv->patch.getCurrentFile(); if (!patchFile.existsAsFile()) { openedPatchFile.replaceWithText(cnv->patch.getCanvasContent(), false, false, "\n"); @@ -150,6 +155,67 @@ struct ExporterBase : public Component return file.getFullPathName(); #endif } + + static void deleteTempFileLater(File const& script) + { + tempFilesToDelete.add(script); + } + + static void deleteTempFiles() + { + for (auto& file : tempFilesToDelete) { + if (file.existsAsFile()) + file.deleteFile(); + if (file.isDirectory()) + file.deleteRecursively(); + } + } + + String startShellScriptWithOutput(String const& scriptText) + { + exportingView->logToConsole("\n\x1b[1;92m> " + scriptText + " \x1b[0m \n\n"); + + File scriptFile = File::createTempFile(".sh"); + deleteTempFileLater(scriptFile); + + auto const bash = String("#!/bin/bash\n"); + scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); + + ChildProcess process; +#if JUCE_WINDOWS + auto sh = toolchainDir.getChildFile("bin").getChildFile("sh.exe"); + auto arguments = StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }; +#else + scriptFile.setExecutePermission(true); + auto arguments = scriptFile.getFullPathName(); +#endif + process.start(arguments, ChildProcess::wantStdOut | ChildProcess::wantStdErr); + return process.readAllProcessOutput(); + } + + void startShellScript(String const& scriptText) + { + exportingView->logToConsole("\n\x1b[1;92m> " + scriptText + " \x1b[0m \n\n"); + + File scriptFile = File::createTempFile(".sh"); + deleteTempFileLater(scriptFile); + +#if JUCE_WINDOWS + auto const gccColourFlags = String("export CFLAGS=\"-fdiagnostics-color=always\"\nexport CXXFLAGS=\"-fdiagnostics-color=always\"\n "); +#else + auto const gccColourFlags = String("export TERM=xterm-256color\nexport GCC_URLS=no\n"); +#endif + auto const bash = String("#!/bin/bash\n"); + scriptFile.replaceWithText(bash + gccColourFlags + scriptText, false, false, "\n"); + +#if JUCE_WINDOWS + auto sh = toolchainDir.getChildFile("bin").getChildFile("sh.exe"); + start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); +#else + scriptFile.setExecutePermission(true); + start(scriptFile.getFullPathName(), ChildProcess::wantStdOut | ChildProcess::wantStdErr | ChildProcess::wantTtyOut); +#endif + } void startExport(File const& outDir) { @@ -245,7 +311,7 @@ struct ExporterBase : public Component static File createMetaJson(DynamicObject::Ptr const& metaJson) { auto const metadata = File::createTempFile(".json"); - Toolchain::deleteTempFileLater(metadata); + deleteTempFileLater(metadata); String const metaString = JSON::toString(var(metaJson.get())); metadata.replaceWithText(metaString, false, false, "\n"); return metadata; diff --git a/Source/Heavy/ExportingProgressView.h b/Source/Heavy/ExportingProgressView.h index 7e5f6e90a..066a449e9 100644 --- a/Source/Heavy/ExportingProgressView.h +++ b/Source/Heavy/ExportingProgressView.h @@ -5,11 +5,516 @@ */ #pragma once +class ExporterConsole : public Component +{ +public: + ExporterConsole() + { + viewport.setViewedComponent(this, false); + viewport.setScrollBarsShown(true, false, false, false); + setVisible(true); + setWantsKeyboardFocus(true); + setMouseCursor(MouseCursor::IBeamCursor); + } + + void clear() + { + string.clear(); + plainText.clear(); + glyphPositions.clear(); + + layout.createLayout(string, viewport.getWidth() - 8); + viewport.setViewPositionProportionately(0.0f, 0.0f); + selectionStart = selectionEnd = 0; + + layout.createLayout(string, viewport.getWidth() - 8); + setSize(viewport.getWidth(), layout.getHeight() + 4); + + repaint(); + } + + void append(String const& text) + { + auto shouldAutoScroll = viewport.getViewPositionY() + viewport.getViewHeight() > getHeight() - 10; + + parseAnsiText(text); + layout.createLayout(string, viewport.getWidth() - 8); + setSize(viewport.getWidth(), layout.getHeight() + 4); + + if(shouldAutoScroll) + viewport.setViewPositionProportionately(0.0f, 1.0f); + } + + void paint(Graphics& g) override + { + // Draw selection highlight first + if (hasSelection()) + { + g.setColour(findColour(TextEditor::highlightColourId)); + + auto selStart = jmin(selectionStart, selectionEnd); + auto selEnd = jmax(selectionStart, selectionEnd); + + for (const auto& rect : getSelectionRectangles(selStart, selEnd)) + { + g.fillRect(rect); + } + } + + layout.draw(g, getLocalBounds().reduced(4, 0).translated(0, -12).toFloat()); + } + + void mouseDown(const MouseEvent& e) override + { + updateGlyphPositions(); + + if(e.mods.isRightButtonDown()) + { + copySelectionToClipboard(); + return; + } + if(e.mods.isShiftDown()) + { + selectionEnd = getCharacterIndexAt(e.position); + repaint(); + return; + } + + selectionStart = selectionEnd = getCharacterIndexAt(e.position); + repaint(); + } + + void mouseDrag(const MouseEvent& e) override + { + selectionEnd = getCharacterIndexAt(e.position); + repaint(); + } + + void mouseDoubleClick(const MouseEvent& e) override + { + int index = getCharacterIndexAt(e.position); + selectWord(index); + } + + bool keyPressed(const KeyPress& key) override + { + if (key == KeyPress('c', ModifierKeys::commandModifier, 0)) { + if (hasSelection()) + { + copySelectionToClipboard(); + return true; + } + } + else if (key == KeyPress('a', ModifierKeys::commandModifier, 0)) { + updateGlyphPositions(); + + selectionStart = 0; + selectionEnd = plainText.length(); + repaint(); + return true; + } + + return false; + } + + Viewport& getViewport() + { + return viewport; + } + +private: + struct GlyphInfo + { + Rectangle bounds; + int charIndex; + }; + + void parseAnsiText(const String& text) + { + String fullText = ansiBuffer + text; + ansiBuffer.clear(); + + String currentSegment; + Colour currentColour = findColour(PlugDataColour::panelTextColourId); + Font currentFont = Fonts::getMonospaceFont(); + + for (int i = 0; i < fullText.length(); ++i) + { + // Check for ANSI escape sequence start + if (fullText[i] == '\x1b' && i + 1 < fullText.length() && fullText[i + 1] == '[') + { + // Append any text before this escape sequence + if (currentSegment.isNotEmpty()) + { + appendWithWordWrap(currentSegment, currentFont, currentColour); + currentSegment = ""; + } + + // Find the end of the escape sequence + int sequenceStart = i + 2; + int sequenceEnd = sequenceStart; + + if(sequenceStart >= fullText.length()) + { + ansiBuffer = fullText.substring(i); + break; + } + + while (sequenceEnd < fullText.length() && + fullText[sequenceEnd] >= 0x30 && fullText[sequenceEnd] <= 0x3F) + { + ++sequenceEnd; + } + + if (sequenceEnd < fullText.length() && + fullText[sequenceEnd] >= 0x40 && fullText[sequenceEnd] <= 0x7E) + { + char command = fullText[sequenceEnd]; + + if (command == 'm') // SGR (Select Graphic Rendition) + { + auto params = fullText.substring(sequenceStart, sequenceEnd); + parseAnsiSGR(params, currentColour, currentFont); + } + + i = sequenceEnd; // Skip past the entire sequence + } + else { + ansiBuffer = fullText.substring(i); + break; + } + } + else + { + currentSegment += fullText[i]; + } + } + + if (currentSegment.isNotEmpty()) + { + appendWithWordWrap(currentSegment, currentFont, currentColour); + } + + plainText = string.getText(); + needsGlyphPositionUpdate = true; + } + + void parseAnsiSGR(const String& params, Colour& currentColour, Font& currentFont) + { + auto defaultTextColour = findColour(PlugDataColour::panelTextColourId); + currentColour = defaultTextColour; + currentFont = Fonts::getMonospaceFont(); + + StringArray codes; + codes.addTokens(params, ";", ""); + + for (const auto& code : codes) + { + int value = code.getIntValue(); + switch (value) + { + case 0: + currentColour = findColour(PlugDataColour::panelTextColourId); + break; + case 1: + currentFont = Fonts::getMonospaceBoldFont(); + break; + case 30: + currentColour = defaultTextColour.interpolatedWith(Colours::black, 0.5); + break; + case 31: + currentColour = defaultTextColour.interpolatedWith(Colours::red, 0.5); + break; + case 32: + currentColour = defaultTextColour.interpolatedWith(Colours::green, 0.5); + break; + case 33: + currentColour = defaultTextColour.interpolatedWith(Colours::yellow, 0.5); + break; + case 34: + currentColour = defaultTextColour.interpolatedWith(Colours::blue, 0.5); + break; + case 35: + currentColour = defaultTextColour.interpolatedWith(Colours::magenta, 0.5); + break; + case 36: + currentColour = defaultTextColour.interpolatedWith(Colours::cyan, 0.5); + break; + case 37: + currentColour = defaultTextColour.interpolatedWith(Colours::white, 0.5); + break; + + case 90: + currentColour = defaultTextColour.interpolatedWith(Colours::darkgrey, 0.7); + break; + case 91: + currentColour = defaultTextColour.interpolatedWith(Colours::red, 0.7); + break; + case 92: + currentColour = defaultTextColour.interpolatedWith(Colours::green, 0.7); + break; + case 93: + currentColour = defaultTextColour.interpolatedWith(Colours::yellow, 0.7); + break; + case 94: + currentColour = defaultTextColour.interpolatedWith(Colours::blue, 0.7); + break; + case 95: + currentColour = defaultTextColour.interpolatedWith(Colours::magenta, 0.7); + break; + case 96: + currentColour = defaultTextColour.interpolatedWith(Colours::cyan, 0.7); + break; + case 97: + currentColour = defaultTextColour.interpolatedWith(Colours::white, 0.7); + break; + default: + break; + } + } + } + + void appendWithWordWrap(const String& text, Font const& font, Colour const& colour) + { + StringArray words; + + // Split on spaces while preserving the spaces + int lastPos = 0; + for (int i = 0; i < text.length(); ++i) + { + if (text[i] == ' ' || text[i] == '\t' || text[i] == '\n') + { + if (i > lastPos) + { + words.add(text.substring(lastPos, i)); + } + words.add(String::charToString(text[i])); + lastPos = i + 1; + } + } + + // Add remaining text + if (lastPos < text.length()) + { + words.add(text.substring(lastPos)); + } + + float maxWidth = viewport.getWidth() - 10; + + for (int wordIdx = 0; wordIdx < words.size(); ++wordIdx) + { + const auto& word = words[wordIdx]; + + if (word == "\n") + { + string.append(word, font, colour); + plainText += word; + currentLineWidth = 0; + continue; + } + + float wordWidth = font.getStringWidthFloat(word); + + // Look ahead: if this is whitespace, check if the next word would fit + if ((word == " " || word == "\t") && wordIdx + 1 < words.size()) + { + float nextWordWidth = font.getStringWidthFloat(words[wordIdx + 1]); + + // If current line + space + next word exceeds width, break NOW + if (currentLineWidth + wordWidth + nextWordWidth > maxWidth && currentLineWidth > 0) + { + string.append("\n", font, colour); + plainText += "\n"; + currentLineWidth = 0; + continue; // Skip the space + } + } + + // If adding this word would exceed the width, insert a newline + if (currentLineWidth + wordWidth > maxWidth && currentLineWidth > 0 && word != " " && word != "\t") + { + string.append("\n", font, colour); + plainText += "\n"; + currentLineWidth = 0; + } + + string.append(word, font, colour); + plainText += word; + currentLineWidth += wordWidth; + } + } + + void updateGlyphPositions() + { + if(!needsGlyphPositionUpdate) return; + + glyphPositions.clear(); + int charIndex = 0; + for (auto& line : layout) { + // TextLayout on macOS/iOS considers whitespace as a character, but on Windows/Linux it does not +#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + float lastX = 4.0f; + for (auto* run : line.runs) { + auto const runText = string.getText().substring(run->stringRange.getStart(), run->stringRange.getEnd()); + int glyphIndex = 0; + for (int i = 0; i < runText.length(); ++i) { + if (CharacterFunctions::isWhitespace(runText[i])) { + GlyphInfo info; + info.bounds = line.getLineBounds().withX(lastX).withWidth(run->font.getStringWidthFloat(runText.substring(i, i + 1))).translated(0, -12); + info.charIndex = charIndex++; + glyphPositions.add(info); + lastX = info.bounds.getRight(); + continue; + } + while (glyphIndex < run->glyphs.size() && +#if JUCE_WINDOWS + run->glyphs.getReference(glyphIndex).glyphCode == 1 +#else + run->glyphs.getReference(glyphIndex).glyphCode == 32 +#endif + ) + glyphIndex++; + + if (glyphIndex < run->glyphs.size()) { + auto const& glyph = run->glyphs.getReference(glyphIndex); + auto const position = glyph.anchor + line.lineOrigin; + GlyphInfo info; + info.bounds = Rectangle(position.x + 4, position.y - run->font.getAscent() - 12, glyph.width, run->font.getHeight()); + info.charIndex = charIndex++; + glyphPositions.add(info); + glyphIndex++; + lastX = info.bounds.getRight(); + } + } + } +#else // JUCE_MAC or JUCE_IOS + for (auto* run : line.runs) { + for (auto& glyph : run->glyphs) { + GlyphInfo info; + auto position = glyph.anchor + line.lineOrigin; + info.bounds = Rectangle(position.x + 4, position.y - run->font.getAscent() - 12, glyph.width, run->font.getHeight()); + info.charIndex = charIndex++; + glyphPositions.add(info); + } + } +#endif + } + + needsGlyphPositionUpdate = false; + } + + int getCharacterIndexAt(Point position) + { + int closestIndex = 0; + for (const auto& glyph : glyphPositions) + { + bool pastCharacter = position.x > (glyph.bounds.getX() - 2); + bool sameLine = glyph.bounds.withX(position.x - 1).withWidth(2).contains(position); + + if (sameLine && pastCharacter) + { + closestIndex = glyph.charIndex; + } + } + + return jlimit(0, plainText.length(), closestIndex); + } + + SmallArray> getSelectionRectangles(int start, int end) + { + SmallArray> rectangles; + + if (glyphPositions.empty()) + return rectangles; + + float currentY = -1; + float lineLeft = std::numeric_limits::max(); + float lineRight = 0; + float glyphHeight = 0.0f; + + for (int i = start; i < end && i < glyphPositions.size(); ++i) + { + const auto& glyph = glyphPositions[i]; + auto const glyphY = glyph.bounds.getY(); + glyphHeight = glyph.bounds.getHeight(); + + if (std::abs(glyphY - currentY) > glyphHeight * 0.5f) + { + rectangles.add(Rectangle(lineLeft, currentY, + lineRight - lineLeft, glyphHeight)); + lineLeft = std::numeric_limits::max(); + lineRight = 0; + } + + currentY = glyphY; + lineLeft = jmin(lineLeft, glyph.bounds.getX()); + lineRight = jmax(lineRight, glyph.bounds.getRight()); + } + + if (currentY >= 0) + { + rectangles.add(Rectangle(lineLeft, currentY, + lineRight - lineLeft, glyphHeight)); + } + + return rectangles; + } + + bool hasSelection() const + { + return selectionStart != selectionEnd; + } + + void selectWord(int index) + { + if (index < 0 || index >= plainText.length()) + return; + + int start = index; + int end = index; + + while (start > 0 && !CharacterFunctions::isWhitespace(plainText[start - 1])) + --start; + + while (end < plainText.length() && !CharacterFunctions::isWhitespace(plainText[end])) + ++end; + + selectionStart = start; + selectionEnd = end; + repaint(); + } + + void copySelectionToClipboard() + { + if (!hasSelection()) + return; + + int start = jmin(selectionStart, selectionEnd); + int end = jmax(selectionStart, selectionEnd); + + String selectedText = plainText.substring(start, end); + SystemClipboard::copyTextToClipboard(selectedText); + } + + Viewport viewport; + AttributedString string; + TextLayout layout; + String plainText; + String ansiBuffer; + HeapArray glyphPositions; + + int selectionStart = 0; + int selectionEnd = 0; + float currentLineWidth = 0; + + bool needsGlyphPositionUpdate = false; +}; + class ExportingProgressView final : public Component , public Thread , public Timer { - TextEditor console; - + + ExporterConsole console; ChildProcess* processToMonitor; public: @@ -29,7 +534,7 @@ class ExportingProgressView final : public Component String userInteractionMessage; - static constexpr int maxLength = 512; + static constexpr int maxLength = 8192; char processOutput[maxLength]; ExportingProgressView() @@ -37,24 +542,11 @@ class ExportingProgressView final : public Component { setVisible(false); addChildComponent(continueButton); - addAndMakeVisible(console); + addAndMakeVisible(console.getViewport()); continueButton.onClick = [this] { showState(NotExporting); }; - - console.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); - console.setColour(TextEditor::outlineColourId, Colours::transparentBlack); - - console.setScrollbarsShown(true); - console.setMultiLine(true); - console.setReadOnly(true); - console.setWantsKeyboardFocus(true); - - // To ensure custom LnF got assigned... - MessageManager::callAsync([this] { - console.setFont(Fonts::getMonospaceFont()); - }); } // For the spinning animation @@ -106,7 +598,7 @@ class ExportingProgressView final : public Component setVisible(state < NotExporting); continueButton.setVisible(state >= Success); if (state == Exporting || state == Flashing) - console.setText(""); + console.clear(); if (console.isShowing()) { console.grabKeyboardFocus(); } @@ -123,9 +615,7 @@ class ExportingProgressView final : public Component if (!_this) return; - _this->console.setText(_this->console.getText() + text); - _this->console.moveCaretToEnd(); - _this->console.setScrollToShowCursor(true); + _this->console.append(text); }); } } @@ -144,7 +634,7 @@ class ExportingProgressView final : public Component g.strokePath(background, PathStrokeType(1.0f)); g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); - g.fillRoundedRectangle(console.getBounds().expanded(2).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(console.getViewport().getBounds().expanded(6).toFloat(), Corners::defaultCornerRadius); if (state == Exporting) { Fonts::drawStyledText(g, "Exporting...", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); @@ -167,7 +657,7 @@ class ExportingProgressView final : public Component } void resized() override { - console.setBounds(proportionOfWidth(0.05f), 80, proportionOfWidth(0.9f), getHeight() - 172); + console.getViewport().setBounds(proportionOfWidth(0.05f), 80, proportionOfWidth(0.9f), getHeight() - 172); continueButton.setBounds(proportionOfWidth(0.42f), getHeight() - 60, proportionOfWidth(0.12f), 24); } }; diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 49d1e9a49..40d2d4345 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -15,7 +15,7 @@ #include "Components/PropertiesPanel.h" #include "Utility/OSUtils.h" -#include "Toolchain.h" +#include "ToolchainInstaller.h" #include "ExportingProgressView.h" #include "ExporterBase.h" #include "CppExporter.h" @@ -196,7 +196,7 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) , exporterPanel(new ExporterSettingsPanel(dynamic_cast(dialog->parentComponent), exportingView.get())) , infoButton(new MainToolbarButton(Icons::Help)) { - hasToolchain = Toolchain::dir.exists(); + hasToolchain = ExporterBase::toolchainDir.exists(); // Don't do this relative to toolchain variable, that won't work on Windows auto const versionFile = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("VERSION"); @@ -261,7 +261,7 @@ HeavyExportDialog::~HeavyExportDialog() Dialogs::dismissFileDialog(); // Clean up temp files - Toolchain::deleteTempFiles(); + ExporterBase::deleteTempFiles(); } void HeavyExportDialog::paint(Graphics& g) diff --git a/Source/Heavy/OWLExporter.h b/Source/Heavy/OWLExporter.h index b8e092fc6..4bd6448cb 100644 --- a/Source/Heavy/OWLExporter.h +++ b/Source/Heavy/OWLExporter.h @@ -45,7 +45,7 @@ class OWLExporter final : public ExporterBase { flashButton.onClick = [this] { auto const tempFolder = File::getSpecialLocation(File::tempDirectory).getChildFile("Heavy-" + Uuid().toString().substring(10)); - Toolchain::deleteTempFileLater(tempFolder); + deleteTempFileLater(tempFolder); startExport(tempFolder); }; } @@ -120,8 +120,7 @@ class OWLExporter final : public ExporterBase { } auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -142,10 +141,9 @@ class OWLExporter final : public ExporterBase { if (compile) { auto const workingDir = File::getCurrentWorkingDirectory(); - auto const bin = Toolchain::dir.getChildFile("bin"); - auto const OWL = Toolchain::dir.getChildFile("lib").getChildFile("OwlProgram"); + auto const bin = toolchainDir.getChildFile("bin"); + auto const OWL = toolchainDir.getChildFile("lib").getChildFile("OwlProgram"); auto make = bin.getChildFile("make" + exeSuffix); - auto compiler = bin.getChildFile("arm-none-eabi-gcc" + exeSuffix); OWL.copyDirectoryTo(outputFile.getChildFile("OwlProgram")); @@ -166,7 +164,7 @@ class OWLExporter final : public ExporterBase { buildScript += pathToString(make) + " -j4" #if JUCE_WINDOWS - + " SHELL=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("bash.exe")).quoted() + + " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted() #endif + " TOOLROOT=" + pathToString(gccPath) + "/" + " BUILD=../" @@ -188,7 +186,7 @@ class OWLExporter final : public ExporterBase { buildScript += " patch"; } - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -217,11 +215,15 @@ class OWLExporter final : public ExporterBase { // rename binary outputFile.getChildFile("patch.bin").moveFileTo(outputFile.getChildFile(name + ".bin")); + if(!compileExitCode) { + exportingView->logToConsole("Compilation finished"); + } + return heavyExitCode && compileExitCode; } else { auto const outputFile = File(outdir); - auto const OWL = Toolchain::dir.getChildFile("lib").getChildFile("OwlProgram"); + auto const OWL = toolchainDir.getChildFile("lib").getChildFile("OwlProgram"); OWL.copyDirectoryTo(outputFile.getChildFile("OwlProgram")); outputFile.getChildFile("ir").deleteRecursively(); diff --git a/Source/Heavy/PdExporter.h b/Source/Heavy/PdExporter.h index 58f304977..4b24ca365 100644 --- a/Source/Heavy/PdExporter.h +++ b/Source/Heavy/PdExporter.h @@ -87,8 +87,7 @@ class PdExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -110,12 +109,12 @@ class PdExporter final : public ExporterBase { outputFile.setAsCurrentWorkingDirectory(); - auto const bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto make = bin.getChildFile("make" + exeSuffix); auto makefile = outputFile.getChildFile("Makefile"); #if JUCE_MAC - Toolchain::startShellScript("make -j4 suppress-wunused=1", this); + startShellScript("make -j4 suppress-wunused=1"); #elif JUCE_WINDOWS File pdDll; if (ProjectInfo::isStandalone) { @@ -124,22 +123,22 @@ class PdExporter final : public ExporterBase { pdDll = File::getSpecialLocation(File::globalApplicationsDirectory).getChildFile("plugdata"); } - auto path = "export PATH=\"$PATH:" + pathToString(Toolchain::dir.getChildFile("bin")) + "\"\n"; - auto cc = "CC=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("gcc.exe")) + " "; - auto cxx = "CXX=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("g++.exe")) + " "; + auto path = "export PATH=\"$PATH:" + pathToString(toolchainDir.getChildFile("bin")) + "\"\n"; + auto cc = "CC=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("gcc.exe")) + " "; + auto cxx = "CXX=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("g++.exe")) + " "; auto pdbindir = "PDBINDIR=\"" + pathToString(pdDll) + "\" "; - auto shell = " SHELL=" + pathToString(Toolchain::dir.getChildFile("bin").getChildFile("bash.exe")).quoted(); + auto shell = " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted(); - Toolchain::startShellScript(path + cc + cxx + pdbindir + pathToString(make) + " -j4 suppress-wunused=1" + shell, this); + startShellScript(path + cc + cxx + pdbindir + pathToString(make) + " -j4 suppress-wunused=1" + shell); #else // Linux or BSD - auto prepareEnvironmentScript = Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getFullPathName() + "\n"; + auto prepareEnvironmentScript = toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getFullPathName() + "\n"; auto buildScript = prepareEnvironmentScript + make.getFullPathName() + " -j4 suppress-wunused=1"; - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); #endif waitForProcessToFinish(-1); diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/ToolchainInstaller.h similarity index 75% rename from Source/Heavy/Toolchain.h rename to Source/Heavy/ToolchainInstaller.h index b0a55b325..5374fe152 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/ToolchainInstaller.h @@ -12,83 +12,6 @@ #include "Utility/Decompress.h" #include "Constants.h" -struct Toolchain { -#if JUCE_WINDOWS - static inline File const dir = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("usr"); -#else - static inline File const dir = ProjectInfo::appDataDir.getChildFile("Toolchain"); -#endif - - static void deleteTempFileLater(File const& script) - { - tempFilesToDelete.add(script); - } - - static void deleteTempFiles() - { - for (auto& file : tempFilesToDelete) { - if (file.existsAsFile()) - file.deleteFile(); - if (file.isDirectory()) - file.deleteRecursively(); - } - } - - static void startShellScript(String const& scriptText, ChildProcess* processToUse = nullptr) - { - File scriptFile = File::createTempFile(".sh"); - Toolchain::deleteTempFileLater(scriptFile); - - auto const bash = String("#!/bin/bash\n"); - scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); - -#if JUCE_WINDOWS - auto sh = Toolchain::dir.getChildFile("bin").getChildFile("sh.exe"); - - if (processToUse) { - processToUse->start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); - } else { - ChildProcess process; - process.start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); - process.waitForProcessToFinish(-1); - } -#else - scriptFile.setExecutePermission(true); - - if (processToUse) { - processToUse->start(scriptFile.getFullPathName()); - } else { - ChildProcess process; - process.start(scriptFile.getFullPathName()); - process.waitForProcessToFinish(-1); - } -#endif - } - - static String startShellScriptWithOutput(String const& scriptText) - { - File scriptFile = File::createTempFile(".sh"); - Toolchain::deleteTempFileLater(scriptFile); - - auto const bash = String("#!/bin/bash\n"); - scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); - - ChildProcess process; -#if JUCE_WINDOWS - auto sh = Toolchain::dir.getChildFile("bin").getChildFile("sh.exe"); - auto arguments = StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }; -#else - scriptFile.setExecutePermission(true); - auto arguments = scriptFile.getFullPathName(); -#endif - process.start(arguments, ChildProcess::wantStdOut | ChildProcess::wantStdErr); - return process.readAllProcessOutput(); - } - -private: - inline static SmallArray tempFilesToDelete; -}; - class ToolchainInstaller final : public Component , public Thread , public Timer { @@ -283,8 +206,8 @@ class ToolchainInstaller final : public Component } #if JUCE_WINDOWS - File usbDriverInstaller = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("install-filter.exe"); - File driverSpec = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("DFU_in_FS_Mode.inf"); + File usbDriverInstaller = toolchainDir.getChildFile("etc").getChildFile("usb_driver").getChildFile("install-filter.exe"); + File driverSpec = toolchainDir.getChildFile("etc").getChildFile("usb_driver").getChildFile("DFU_in_FS_Mode.inf"); // Since we interact with ComponentPeer, better call it from the message thread MessageManager::callAsync([this, usbDriverInstaller, driverSpec]() mutable { @@ -297,8 +220,8 @@ class ToolchainInstaller final : public Component // This makes sure we can use dfu-util without admin privileges // Kinda sucks that we need to sudo this, but there's no other way AFAIK - auto askpassScript = Toolchain::dir.getChildFile("scripts").getChildFile("askpass.sh"); - auto udevInstallScript = Toolchain::dir.getChildFile("scripts").getChildFile("install_udev_rule.sh"); + auto askpassScript = toolchainDir.getChildFile("scripts").getChildFile("askpass.sh"); + auto udevInstallScript = toolchainDir.getChildFile("scripts").getChildFile("install_udev_rule.sh"); askpassScript.setExecutePermission(true); udevInstallScript.setExecutePermission(true); @@ -308,7 +231,9 @@ class ToolchainInstaller final : public Component } #elif JUCE_MAC - Toolchain::startShellScript("xcode-select --install"); + ChildProcess process; + process.start("xcode-select --install"); + process.waitForProcessToFinish(-1); #endif installProgress = 0.0f; diff --git a/Source/Heavy/WASMExporter.h b/Source/Heavy/WASMExporter.h index b6cb23dad..0e89f2ee5 100644 --- a/Source/Heavy/WASMExporter.h +++ b/Source/Heavy/WASMExporter.h @@ -85,7 +85,6 @@ class WASMExporter final : public ExporterBase { return true; auto compileString = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + compileString + "\n"); #if JUCE_WINDOWS auto buildScript = "source " + emsdkPath.replaceCharacter('\\', '/') + "/emsdk_env.sh; " + compileString; @@ -93,7 +92,7 @@ class WASMExporter final : public ExporterBase { auto buildScript = "source " + emsdkPath + "/emsdk_env.sh; " + compileString; #endif - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index b1ae1bb31..ddd8c2997 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -287,7 +287,7 @@ class Instance : public AsyncUpdater { void* printReceiver = nullptr; void* dataBufferReceiver = nullptr; - inline static String const defaultPatch = "#N canvas 827 239 734 565 12;"; + static inline String const defaultPatch = "#N canvas 827 239 734 565 12;"; bool initialiseIntoPluginmode = false; bool isPerformingGlobalSync = false; diff --git a/Source/Utility/Fonts.h b/Source/Utility/Fonts.h index 12df3d128..b4a4afa08 100644 --- a/Source/Utility/Fonts.h +++ b/Source/Utility/Fonts.h @@ -73,6 +73,7 @@ struct Fonts { semiBoldTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterSemiBold_ttf, BinaryData::InterSemiBold_ttfSize); iconTypeface = Typeface::createSystemTypefaceFor(BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize); monoTypeface = Typeface::createSystemTypefaceFor(BinaryData::RobotoMono_Regular_ttf, BinaryData::RobotoMono_Regular_ttfSize); + monoBoldTypeface = Typeface::createSystemTypefaceFor(BinaryData::RobotoMono_Bold_ttf, BinaryData::RobotoMono_Bold_ttfSize); variableTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterVariable_ttf, BinaryData::InterVariable_ttfSize); tabularTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize); @@ -85,6 +86,7 @@ struct Fonts { static Font getSemiBoldFont() { return Font(instance->semiBoldTypeface); } static Font getIconFont() { return Font(instance->iconTypeface); } static Font getMonospaceFont() { return Font(instance->monoTypeface); } + static Font getMonospaceBoldFont() { return Font(instance->monoBoldTypeface); } static Font getVariableFont() { return Font(instance->variableTypeface); } static Font getTabularNumbersFont() { return Font(instance->tabularTypeface); } @@ -239,6 +241,7 @@ struct Fonts { Typeface::Ptr semiBoldTypeface; Typeface::Ptr iconTypeface; Typeface::Ptr monoTypeface; + Typeface::Ptr monoBoldTypeface; Typeface::Ptr variableTypeface; Typeface::Ptr tabularTypeface;