Make text editor a bit more useful, and resizable
Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
parent
268c31c029
commit
6f9013da16
4 changed files with 326 additions and 73 deletions
|
@ -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"
|
||||
]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue