|
|
@@ -26,66 +26,198 @@ |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
// defaults |
|
|
|
#define DEFAULT_LANG "C++" |
|
|
|
#define DEFAULT_TEXT "" \ |
|
|
|
"// Welcome to a real text editor inside Cardinal\n\n" \ |
|
|
|
"#define I_AM_A_MACRO\n\n" \ |
|
|
|
"int and_i_am_a_variable;\n\n" \ |
|
|
|
"/* look ma, a comment! */\n" \ |
|
|
|
"int such_highlight_much_wow() { return 1337; }\n" |
|
|
|
#define DEFAULT_WIDTH 30 |
|
|
|
|
|
|
|
// utils |
|
|
|
static const TextEditor::LanguageDefinition& getLangFromString(const std::string& lang) |
|
|
|
{ |
|
|
|
if (lang == "AngelScript") |
|
|
|
return TextEditor::LanguageDefinition::AngelScript(); |
|
|
|
if (lang == "C") |
|
|
|
return TextEditor::LanguageDefinition::C(); |
|
|
|
if (lang == "C++") |
|
|
|
return TextEditor::LanguageDefinition::CPlusPlus(); |
|
|
|
if (lang == "GLSL") |
|
|
|
return TextEditor::LanguageDefinition::GLSL(); |
|
|
|
if (lang == "HLSL") |
|
|
|
return TextEditor::LanguageDefinition::HLSL(); |
|
|
|
if (lang == "Lua") |
|
|
|
return TextEditor::LanguageDefinition::Lua(); |
|
|
|
if (lang == "SQL") |
|
|
|
return TextEditor::LanguageDefinition::SQL(); |
|
|
|
|
|
|
|
static const TextEditor::LanguageDefinition none; |
|
|
|
return none; |
|
|
|
} |
|
|
|
|
|
|
|
struct TextEditorModule : Module { |
|
|
|
enum ParamIds { |
|
|
|
NUM_PARAMS |
|
|
|
}; |
|
|
|
enum InputIds { |
|
|
|
NUM_INPUTS |
|
|
|
}; |
|
|
|
enum OutputIds { |
|
|
|
NUM_OUTPUTS |
|
|
|
}; |
|
|
|
enum LightIds { |
|
|
|
NUM_LIGHTS |
|
|
|
}; |
|
|
|
std::string file; |
|
|
|
std::string lang = DEFAULT_LANG; |
|
|
|
std::string text = DEFAULT_TEXT; |
|
|
|
int width = DEFAULT_WIDTH; |
|
|
|
#ifndef HEADLESS |
|
|
|
WeakPtr<ImGuiTextEditor> widgetPtr; |
|
|
|
#endif |
|
|
|
|
|
|
|
TextEditorModule() |
|
|
|
json_t* dataToJson() override |
|
|
|
{ |
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
|
json_t* rootJ = json_object(); |
|
|
|
json_object_set_new(rootJ, "filepath", json_string(file.c_str())); |
|
|
|
json_object_set_new(rootJ, "lang", json_string(lang.c_str())); |
|
|
|
json_object_set_new(rootJ, "text", json_string(text.c_str())); |
|
|
|
json_object_set_new(rootJ, "width", json_integer(width)); |
|
|
|
return rootJ; |
|
|
|
} |
|
|
|
|
|
|
|
~TextEditorModule() override |
|
|
|
void dataFromJson(json_t* const rootJ) override |
|
|
|
{ |
|
|
|
if (json_t* const widthJ = json_object_get(rootJ, "width")) |
|
|
|
width = json_integer_value(widthJ); |
|
|
|
|
|
|
|
if (json_t* const langJ = json_object_get(rootJ, "lang")) |
|
|
|
{ |
|
|
|
lang = json_string_value(langJ); |
|
|
|
#ifndef HEADLESS |
|
|
|
// if (ImGuiTextEditor* const widget = widgetPtr) |
|
|
|
// widget->SetLanguageDefinition(getLangFromString(lang)); |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
if (json_t* const filepathJ = json_object_get(rootJ, "filepath")) |
|
|
|
{ |
|
|
|
const char* const filepath = json_string_value(filepathJ); |
|
|
|
|
|
|
|
if (filepath[0] != '\0') |
|
|
|
{ |
|
|
|
std::ifstream f(filepath); |
|
|
|
|
|
|
|
if (f.good()) |
|
|
|
{ |
|
|
|
file = filepath; |
|
|
|
text = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); |
|
|
|
#ifndef HEADLESS |
|
|
|
if (ImGuiTextEditor* const widget = widgetPtr) |
|
|
|
widget->setFileWithKnownText(file, text); |
|
|
|
#endif |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (json_t* const textJ = json_object_get(rootJ, "text")) |
|
|
|
{ |
|
|
|
text = json_string_value(textJ); |
|
|
|
#ifndef HEADLESS |
|
|
|
if (ImGuiTextEditor* const widget = widgetPtr) |
|
|
|
widget->setText(text); |
|
|
|
#endif |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void process(const ProcessArgs&) override |
|
|
|
bool loadFileFromMenuAction(const char* const filepath) |
|
|
|
{ |
|
|
|
} |
|
|
|
std::ifstream f(filepath); |
|
|
|
|
|
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorModule) |
|
|
|
if (f.good()) |
|
|
|
{ |
|
|
|
file = filepath; |
|
|
|
text = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
#ifndef HEADLESS |
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
#ifndef HEADLESS |
|
|
|
struct TextEditorWidget : ImGuiTextEditor |
|
|
|
{ |
|
|
|
TextEditorWidget() : ImGuiTextEditor() {} |
|
|
|
struct TextEditorLangSelectItem : MenuItem { |
|
|
|
TextEditorModule* const module; |
|
|
|
ImGuiTextEditor* const widget; |
|
|
|
|
|
|
|
TextEditorLangSelectItem(TextEditorModule* const textEditorModule, |
|
|
|
ImGuiTextEditor* const textEditorWidget, |
|
|
|
const char* const textToUse) |
|
|
|
: module(textEditorModule), |
|
|
|
widget(textEditorWidget) |
|
|
|
{ |
|
|
|
text = textToUse; |
|
|
|
} |
|
|
|
|
|
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorWidget) |
|
|
|
void onAction(const event::Action &e) override |
|
|
|
{ |
|
|
|
module->lang = text; |
|
|
|
// widget->SetLanguageDefinition(getLangFromString(text)); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct TextEditorLangSelectMenu : Menu { |
|
|
|
TextEditorLangSelectMenu(TextEditorModule* const module, ImGuiTextEditor* const widget) |
|
|
|
{ |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "None")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "AngelScript")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "C")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "C++")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "GLSL")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "HLSL")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "Lua")); |
|
|
|
addChild(new TextEditorLangSelectItem(module, widget, "SQL")); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct TextEditorLangSelectMenuItem : MenuItem { |
|
|
|
TextEditorModule* const module; |
|
|
|
ImGuiTextEditor* const widget; |
|
|
|
|
|
|
|
TextEditorLangSelectMenuItem(TextEditorModule* const textEditorModule, ImGuiTextEditor* const textEditorWidget) |
|
|
|
: module(textEditorModule), |
|
|
|
widget(textEditorWidget) |
|
|
|
{ |
|
|
|
text = "Syntax Highlight"; |
|
|
|
} |
|
|
|
|
|
|
|
void onAction(const event::Action &e) override |
|
|
|
{ |
|
|
|
// TODO |
|
|
|
// new TextEditorLangSelectMenu(module, widget); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
struct TextEditorLoadFileItem : MenuItem { |
|
|
|
TextEditorWidget* widget = nullptr; |
|
|
|
TextEditorModule* const module; |
|
|
|
ImGuiTextEditor* const widget; |
|
|
|
|
|
|
|
TextEditorLoadFileItem(TextEditorModule* const textEditorModule, ImGuiTextEditor* const textEditorWidget) |
|
|
|
: module(textEditorModule), |
|
|
|
widget(textEditorWidget) |
|
|
|
{ |
|
|
|
text = "Load File"; |
|
|
|
} |
|
|
|
|
|
|
|
void onAction(const event::Action &e) override |
|
|
|
{ |
|
|
|
WeakPtr<TextEditorWidget> widget = this->widget; |
|
|
|
async_dialog_filebrowser(false, nullptr, "Load File", [widget](char* path) |
|
|
|
TextEditorModule* const module = this->module;; |
|
|
|
WeakPtr<ImGuiTextEditor> widget = this->widget; |
|
|
|
|
|
|
|
async_dialog_filebrowser(false, nullptr, text.c_str(), [module, widget](char* path) |
|
|
|
{ |
|
|
|
if (path) |
|
|
|
{ |
|
|
|
if (widget) |
|
|
|
if (module->loadFileFromMenuAction(path)) |
|
|
|
{ |
|
|
|
std::ifstream f(path); |
|
|
|
if (f.good()) |
|
|
|
{ |
|
|
|
const std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); |
|
|
|
widget->setText(str); |
|
|
|
} |
|
|
|
if (widget) |
|
|
|
widget->setFileWithKnownText(module->file, module->text); |
|
|
|
} |
|
|
|
free(path); |
|
|
|
} |
|
|
@@ -95,35 +227,144 @@ struct TextEditorLoadFileItem : MenuItem { |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
/** |
|
|
|
* Code adapted from VCVRack's Blank.cpp |
|
|
|
* Copyright (C) 2016-2021 VCV. |
|
|
|
* |
|
|
|
* This program is free software: you can redistribute it and/or |
|
|
|
* modify it under the terms of the GNU General Public License as |
|
|
|
* published by the Free Software Foundation; either version 3 of |
|
|
|
* the License, or (at your option) any later version. |
|
|
|
*/ |
|
|
|
|
|
|
|
struct ModuleResizeHandle : OpaqueWidget { |
|
|
|
TextEditorModule* const module; |
|
|
|
ModuleWidget* const widget; |
|
|
|
const bool right; |
|
|
|
Vec dragPos; |
|
|
|
Rect originalBox; |
|
|
|
|
|
|
|
ModuleResizeHandle(TextEditorModule* const textEditorModule, ModuleWidget* const moduleWidget, const bool r) |
|
|
|
: module(textEditorModule), |
|
|
|
widget(moduleWidget), |
|
|
|
right(r) |
|
|
|
{ |
|
|
|
box.size = Vec(RACK_GRID_WIDTH * 1, RACK_GRID_HEIGHT); |
|
|
|
} |
|
|
|
|
|
|
|
void onDragStart(const DragStartEvent& e) override |
|
|
|
{ |
|
|
|
if (e.button != GLFW_MOUSE_BUTTON_LEFT) |
|
|
|
return; |
|
|
|
|
|
|
|
dragPos = APP->scene->rack->getMousePos(); |
|
|
|
originalBox = widget->box; |
|
|
|
} |
|
|
|
|
|
|
|
void onDragMove(const DragMoveEvent& e) override |
|
|
|
{ |
|
|
|
static const float kMinWidth = 10 * RACK_GRID_WIDTH; |
|
|
|
|
|
|
|
Vec newDragPos = APP->scene->rack->getMousePos(); |
|
|
|
float deltaX = newDragPos.x - dragPos.x; |
|
|
|
|
|
|
|
Rect newBox = originalBox; |
|
|
|
Rect oldBox = widget->box; |
|
|
|
if (right) { |
|
|
|
newBox.size.x += deltaX; |
|
|
|
newBox.size.x = std::fmax(newBox.size.x, kMinWidth); |
|
|
|
newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; |
|
|
|
} |
|
|
|
else { |
|
|
|
newBox.size.x -= deltaX; |
|
|
|
newBox.size.x = std::fmax(newBox.size.x, kMinWidth); |
|
|
|
newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; |
|
|
|
newBox.pos.x = originalBox.pos.x + originalBox.size.x - newBox.size.x; |
|
|
|
} |
|
|
|
|
|
|
|
// Set box and test whether it's valid |
|
|
|
widget->box = newBox; |
|
|
|
if (!APP->scene->rack->requestModulePos(widget, newBox.pos)) { |
|
|
|
widget->box = oldBox; |
|
|
|
} |
|
|
|
module->width = std::round(widget->box.size.x / RACK_GRID_WIDTH); |
|
|
|
} |
|
|
|
|
|
|
|
void draw(const DrawArgs& args) override |
|
|
|
{ |
|
|
|
for (float x = 5.0; x <= 10.0; x += 5.0) |
|
|
|
{ |
|
|
|
nvgBeginPath(args.vg); |
|
|
|
const float margin = 5.0; |
|
|
|
nvgMoveTo(args.vg, x + 0.5, margin + 0.5); |
|
|
|
nvgLineTo(args.vg, x + 0.5, box.size.y - margin + 0.5); |
|
|
|
nvgStrokeWidth(args.vg, 1.0); |
|
|
|
nvgStrokeColor(args.vg, nvgRGBAf(0.5, 0.5, 0.5, 0.5)); |
|
|
|
nvgStroke(args.vg); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
struct TextEditorModuleWidget : ModuleWidget { |
|
|
|
TextEditorWidget* textEditorWidget = nullptr; |
|
|
|
TextEditorModule* textEditorModule = nullptr; |
|
|
|
ImGuiTextEditor* textEditorWidget = nullptr; |
|
|
|
Widget* panelBorder; |
|
|
|
ModuleResizeHandle* rightHandle; |
|
|
|
|
|
|
|
TextEditorModuleWidget(TextEditorModule* const module) |
|
|
|
{ |
|
|
|
setModule(module); |
|
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Ildaeil.svg"))); |
|
|
|
addChild(panelBorder = new PanelBorder); |
|
|
|
addChild(rightHandle = new ModuleResizeHandle(module, this, true)); |
|
|
|
addChild(new ModuleResizeHandle(module, this, false)); |
|
|
|
|
|
|
|
if (module != nullptr) |
|
|
|
{ |
|
|
|
textEditorWidget = new TextEditorWidget(); |
|
|
|
textEditorWidget->box.pos = Vec(2 * RACK_GRID_WIDTH, 0); |
|
|
|
textEditorWidget->box.size = Vec(box.size.x - 2 * RACK_GRID_WIDTH, box.size.y); |
|
|
|
box.size = Vec(RACK_GRID_WIDTH * module->width, RACK_GRID_HEIGHT); |
|
|
|
textEditorModule = module; |
|
|
|
textEditorWidget = new ImGuiTextEditor(); |
|
|
|
textEditorWidget->setFileWithKnownText(module->file, module->text); |
|
|
|
textEditorWidget->box.pos = Vec(RACK_GRID_WIDTH, 0); |
|
|
|
textEditorWidget->box.size = Vec((module->width - 2) * RACK_GRID_WIDTH, box.size.y); |
|
|
|
addChild(textEditorWidget); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
box.size = Vec(RACK_GRID_WIDTH * DEFAULT_WIDTH, RACK_GRID_HEIGHT); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
addChild(createWidget<ScrewBlack>(Vec(0, 0))); |
|
|
|
addChild(createWidget<ScrewBlack>(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
|
|
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0))); |
|
|
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
|
|
|
void appendContextMenu(Menu *menu) override |
|
|
|
{ |
|
|
|
menu->addChild(new MenuSeparator); |
|
|
|
menu->addChild(new TextEditorLoadFileItem(textEditorModule, textEditorWidget)); |
|
|
|
// TODO |
|
|
|
// menu->addChild(new TextEditorLangSelectMenuItem(textEditorModule, textEditorWidget)); |
|
|
|
} |
|
|
|
|
|
|
|
void appendContextMenu(Menu *menu) override { |
|
|
|
menu->addChild(new MenuEntry); |
|
|
|
void step() override |
|
|
|
{ |
|
|
|
if (textEditorModule) |
|
|
|
{ |
|
|
|
box.size.x = textEditorModule->width * RACK_GRID_WIDTH; |
|
|
|
textEditorWidget->box.size.x = (textEditorModule->width - 2) * RACK_GRID_WIDTH; |
|
|
|
} |
|
|
|
|
|
|
|
panelBorder->box.size = box.size; |
|
|
|
rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; |
|
|
|
|
|
|
|
TextEditorLoadFileItem* loadFileItem = new TextEditorLoadFileItem; |
|
|
|
loadFileItem->text = "Load File"; |
|
|
|
loadFileItem->widget = textEditorWidget; |
|
|
|
menu->addChild(loadFileItem); |
|
|
|
ModuleWidget::step(); |
|
|
|
} |
|
|
|
|
|
|
|
void draw(const DrawArgs& args) override |
|
|
|
{ |
|
|
|
nvgBeginPath(args.vg); |
|
|
|
nvgRect(args.vg, 0.0, 0.0, box.size.x, box.size.y); |
|
|
|
nvgFillColor(args.vg, nvgRGB(0x20, 0x20, 0x20)); |
|
|
|
nvgFill(args.vg); |
|
|
|
ModuleWidget::draw(args); |
|
|
|
} |
|
|
|
|
|
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorModuleWidget) |
|
|
|