ArthurSonzogni / FTXUI

:computer: C++ Functional Terminal User Interface. :heart:
MIT License
6.64k stars 399 forks source link

Combine canvas and other elements #797

Closed SPYFF closed 7 months ago

SPYFF commented 8 months ago

Hi,

I want to render interactive DAG graphs (like graphviz dot or d3/cytoscape dagre layout) inside the terminal. Seems like the library can provide all the components needed to render something like this, however I'm not sure if the puzzle pieces fit together. I have three possible approaches in mind, my question is whether one of them works?

Option 1: On the same screen, first render the canvas with the lines (Braille or box) and then, on top of that, render the graph nodes as window components (without title).

Option 2: Only a canvas component, first draw the lines and then render the nodes as boxed text, somehow get the three lines (top, bottom and the text label) in string and put it on top of the lines. Is it possible to render the components as plain text?

Option 3: Is it possible to draw any component on a canvas? To me it looks like canvas at the moment supports limited number of shapes, and not possible to draw other elements (even as static ones, like boxed text).

What approach would work/be possible?

Thanks in advance!

ArthurSonzogni commented 7 months ago

Hello @SPYFF ! I created something similar in the past. See: https://arthursonzogni.com/Diagon/#GraphDAG

In your case, I am guessing you are going to make the "node" draggable. I think options 2 is going to work.

See this example source

I would use the Container::Stacked({...}) with:

SPYFF commented 7 months ago

Thanks for the helpful reply! Yes, I'm aware of your Diagon project, and the DAG code is the best currently publicly available on the web (I've tried similar projects, like graph-easy and vijual, but they produce inferior results for large graphs). However, its optimized for compact, static output, which is great for many cases, but I compared it to graphviz output (its unfair, I know) and that output would be better for my interactive usecase. So my plan is to convert graphviz output directly to text.

Thanks for the example and the recommendation, I'll go ahead with it.

SPYFF commented 7 months ago

With your help I managed to do it:

Screencast from 2024-01-05 18-18-54.webm

In case someone want to reproduce it, here is the snippet:

#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/component/component_base.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/dom/node.hpp>
#include <ftxui/dom/canvas.hpp>
#include <iterator>
#include <string>

using namespace ftxui;

int main() {
    auto node1 = Renderer([] { return text("node1");});
    auto node2 = Renderer([] { return text("node2");});
    bool resize = false;

    int win1h = 3;
    int win1w = std::size(std::string("node1")) + 2;
    int win1x = 10, win1y = 5;
    int win2h = 3;
    int win2w = std::size(std::string("node2")) + 2;
    int win2x = 30, win2y = 5;

    auto win1 = Window({
        .inner = node1,
        .left = &win1x,
        .top = &win1y,
        .width = win1w,
        .height = win1h,
        .resize_left = resize, .resize_right = resize, .resize_top = resize, .resize_down = resize,
    });

    auto win2 = Window({
        .inner = node2,
        .left = &win2x,
        .top = &win2y,
        .width = win2w,
        .height = win2h,
        .resize_left = resize, .resize_right = resize, .resize_top = resize, .resize_down = resize,
    });

    auto edges = Renderer([&] {
        auto c = Canvas(400,400);
        int from_x = (win1x + win1w / 2) * 2;
        int from_y = (win1y + 3 / 2) * 4;
        int to_x = (win2x + win2w / 2) * 2;
        int to_y = (win2y + 3 / 2) * 4;
        c.DrawPointLine(from_x, from_y, to_x, to_y, Color::Red);
        return canvas(std::move(c));
    });

    auto window_container = Container::Stacked({
        win1,
        win2,
        edges,
    });

    auto screen = ScreenInteractive::Fullscreen();
    screen.Loop(window_container);

    return EXIT_SUCCESS;
}

Thanks again!

ArthurSonzogni commented 7 months ago

Awesome! I love how it renders. This triggers in me many nice project ideas. Keep us updated what you will achieve from this!

SPYFF commented 6 months ago

@ArthurSonzogni Sure! I'm thinking about an interactive graph visualizer, something like that:

Screencast from 2024-02-10 14-06-26.webm

Sure, the arrowheads are off, and there are other flaws (all because I forgot everything I learned in trigonometry since school), however your lib doing well rendering the stuff! The layout initially created by graphviz dot, then I parse it to a struct which will be feed to the canvas and that struct updated interactively.

ArthurSonzogni commented 6 months ago

Wow! That's really cool!