Make text editor a bit more useful, and resizable

Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
falkTX 2021-12-17 20:55:51 +00:00
parent 268c31c029
commit 6f9013da16
No known key found for this signature in database
GPG key ID: CDBAA37ABC74FBA0
4 changed files with 326 additions and 73 deletions

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"
]

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);
}

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.
*/

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;
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorWidget)
TextEditorLangSelectItem(TextEditorModule* const textEditorModule,
ImGuiTextEditor* const textEditorWidget,
const char* const textToUse)
: module(textEditorModule),
widget(textEditorWidget)
{
text = textToUse;
}
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);
}
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)));
else
{
box.size = Vec(RACK_GRID_WIDTH * DEFAULT_WIDTH, RACK_GRID_HEIGHT);
}
}
void appendContextMenu(Menu *menu) override {
menu->addChild(new MenuEntry);
void appendContextMenu(Menu *menu) override
{
menu->addChild(new MenuSeparator);
menu->addChild(new TextEditorLoadFileItem(textEditorModule, textEditorWidget));
// TODO
// menu->addChild(new TextEditorLangSelectMenuItem(textEditorModule, textEditorWidget));
}
TextEditorLoadFileItem* loadFileItem = new TextEditorLoadFileItem;
loadFileItem->text = "Load File";
loadFileItem->widget = textEditorWidget;
menu->addChild(loadFileItem);
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;
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)