Browse Source

Make text editor a bit more useful, and resizable

Signed-off-by: falkTX <falktx@falktx.com>
tags/22.02
falkTX 3 years ago
parent
commit
6f9013da16
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
4 changed files with 325 additions and 72 deletions
  1. +1
    -1
      plugins/Cardinal/plugin.json
  2. +31
    -23
      plugins/Cardinal/src/ImGuiTextEditor.cpp
  3. +4
    -0
      plugins/Cardinal/src/ImGuiTextEditor.hpp
  4. +289
    -48
      plugins/Cardinal/src/TextEditor.cpp

+ 1
- 1
plugins/Cardinal/plugin.json View File

@@ -80,7 +80,7 @@
"slug": "TextEditor",
"disabled": false,
"name": "Text Editor",
"description": "A embed text editor inside Cardinal",
"description": "An embed text editor inside Cardinal",
"tags": [
"Utility"
]


+ 31
- 23
plugins/Cardinal/src/ImGuiTextEditor.cpp View File

@@ -25,40 +25,20 @@
#include "ImGuiTextEditor.hpp"
#include "DearImGuiColorTextEditor/TextEditor.h"

#include <fstream>

// --------------------------------------------------------------------------------------------------------------------

struct ImGuiTextEditor::PrivateData {
ImGuiTextEditor* const self;
TextEditor editor;
std::string file;

explicit PrivateData(ImGuiTextEditor* const s)
: self(s)
{
// https://github.com/BalazsJako/ColorTextEditorDemo/blob/master/main.cpp

editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
editor.SetText(""
"// 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"
);
editor.SetCursorPosition(TextEditor::Coordinates(8, 0));
}

DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
};

// --------------------------------------------------------------------------------------------------------------------

ImGuiTextEditor::ImGuiTextEditor()
: ImGuiWidget(),
pData(new PrivateData(this)) {}
pData(new PrivateData) {}

ImGuiTextEditor::~ImGuiTextEditor()
{
@@ -67,8 +47,35 @@ ImGuiTextEditor::~ImGuiTextEditor()

// --------------------------------------------------------------------------------------------------------------------

bool ImGuiTextEditor::setFile(const std::string& file)
{
std::ifstream f(file);

if (! f.good())
{
pData->file.clear();
return false;
}

pData->file = file;
pData->editor.SetText(std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()));
return true;
}

void ImGuiTextEditor::setFileWithKnownText(const std::string& file, const std::string& text)
{
pData->file = file;
pData->editor.SetText(text);
}

std::string ImGuiTextEditor::getFile() const
{
return pData->file;
}

void ImGuiTextEditor::setText(const std::string& text)
{
pData->file.clear();
pData->editor.SetText(text);
}

@@ -79,6 +86,7 @@ std::string ImGuiTextEditor::getText() const

void ImGuiTextEditor::setTextLines(const std::vector<std::string>& lines)
{
pData->file.clear();
pData->editor.SetTextLines(lines);
}



+ 4
- 0
plugins/Cardinal/src/ImGuiTextEditor.hpp View File

@@ -38,6 +38,10 @@ struct ImGuiTextEditor : ImGuiWidget
ImGuiTextEditor();
~ImGuiTextEditor() override;

bool setFile(const std::string& file);
void setFileWithKnownText(const std::string& file, const std::string& text);
std::string getFile() const;

/**
Methods from internal TextEdit.
*/


+ 289
- 48
plugins/Cardinal/src/TextEditor.cpp View File

@@ -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)


Loading…
Cancel
Save