Framework GUI com EasyX para C++: O Guia do yNodeGUI

O yNodeGUI v2.0 é um framework leve para C++ baseado na biblioteca gráfica EasyX, voltado para o desenvolvimento rápido de interfaces de sistemas de gerenciamento simples. Ele oferece componentes GUI prontos, um sistema de menus hierárquicos infinitos e uma renderização fixa simplificada.

Pré‑requisitos

  • Conhecimento básico de orientação a objetos e familiaridade com EasyX ou IMGUI.
  • Visual Studio instalado com a biblioteca EasyX (disponível em easyx.cn).
  • Configurar o projeto para usar conjunto de caracteres multibyte e, para dsitribuição, definir a opção de geração de código como MT (no modo Release).

Funcionalidades

  • Componentes GUI prontos: botões, caixas redimensionáveis, grades, textos, imagens.
  • Baixa curva de aprendizado: o sistema NodeGUI abstrai menus em uma árvore N‑ária, facilitando o gerenciamento de profundidade.
  • Gerenciador de menus eficiente: renderização automática de menus com suporte a submenus de vários níveis.

Uso básico da API

0. Incluir o cabeçalho

#include "yNodeGUI.h"

1. Criar a tela no main

Tela canvas = { 1000, 600, 90, RGB(255,255,255) };
canvas.Mostrar(AoIniciar, AoAtualizar, AoRenderizar, true);

2. Definir os nós do menu em AoIniciar

No* raiz = new No();
GerenciadorMenu* menu = new GerenciadorMenu(raiz, &canvas);

No* m1 = new No(raiz, "Cadastro de Cursos");
No* m2 = new No(raiz, "Visualizar Cursos", browsing, true, fresh);
No* m3 = new No(raiz, "Consultar Cursos", queryshow, true, fresh_query);
No* m4 = new No(raiz, "Matrícula de Alunos");
No* m6 = new No(raiz, "Sair do Sistema", exit_system, true);

No* x1 = new No(m1, "Inserir Curso", input, true);
No* x2 = new No(m1, "Alterar Curso", set_course, true);
No* x3 = new No(m1, "Excluir Curso", del_course, true);
No* x4 = new No(m1, "Voltar", last_menu, true);

No* n1 = new No(m4, "Escolher Disciplina", choose, true);
No* n2 = new No(m4, "Ver Disciplinas Selecionadas", view_selected, true, fresh_selected);
No* n3 = new No(m4, "Voltar", last_menu, true);

3. Registrar e renderizar componentes

// Em AoRenderizar:
canvas.Ambiente(0).Registrar(id, botao);
canvas.Ambiente(0).Desenhar(id);

Exemplos completos encontram‑se no repositório de demonstração: yNodeGUI_Sample.

Estruturas básicas

O framework define seus próprios tipos Vector2, Rect, uma classe base EntidadeBase (que atribui um ID único a cada objeto) e uma interface ComponenteGUI.

// ---- Estruturas fundamentais ----
typedef struct {
    int x, y;
} Vector2;

typedef struct {
    Vector2 origem, fim, centro;
    int largura, altura;
} Rect;

// Classe base para todo objeto instanciável
class EntidadeBase {
private:
    static int contadorGlobal;
    static std::set<int> idsUsados;
    int idInstancia;
    static int AtribuirID() {
        do {
            if (contadorGlobal == INT_MIN) contadorGlobal = INT_MAX;
            contadorGlobal--;
        } while (idsUsados.count(contadorGlobal));
        idsUsados.insert(contadorGlobal);
        return contadorGlobal;
    }
public:
    EntidadeBase() : idInstancia(AtribuirID()) {}
    ~EntidadeBase() { idsUsados.erase(idInstancia); }
    int ObterID() const { return idInstancia; }
};
int EntidadeBase::contadorGlobal = INT_MAX;
std::set<int> EntidadeBase::idsUsados;

// Interface para componentes visuais
class ComponenteGUI : public EntidadeBase {
public:
    virtual void AoRenderizar() = 0;
    virtual void AoEvento(ExMessage* msg) = 0;
};

A classe Tela (Canvas)

Responsável por gerenciar a janela, o loop principal e o ambiente de renderização.

class Tela : public EntidadeBase {
private:
    int largura, altura, fps, inicioQuadro, tempoQuadro;
    bool ativo = true;
    int deltaTempo;
    COLORREF corFundo;
    ExMessage mensagem;
    HWND hwnd;
    std::map<int, ComponenteGUI*> ambiente0, ambiente1, ambiente2, ambiente3;
    std::map<int, std::map<int, ComponenteGUI*>> ambientes{{0,ambiente0},{1,ambiente1},{2,ambiente2},{3,ambiente3}};
    std::map<int, std::vector<ComponenteGUI*>> coletas{{0,{}},{1,{}},{2,{}},{3,{}}};
    std::queue<ComponenteGUI*> filaRender, filaEventos;
    int ambienteAtual = 0;
    void RenderizarTodos() { while (!filaRender.empty()) { filaRender.front()->AoRenderizar(); filaRender.pop(); } }
    void TransmitirEvento(ExMessage* msg) { while (!filaEventos.empty()) { filaEventos.front()->AoEvento(msg); filaEventos.pop(); } }
protected:
    void (*AoIniciar)(Tela&);
    void (*AoRenderizar)(Tela&);
    void (*AoAtualizar)(Tela&);
public:
    // Construtor, destrutor, métodos de ambiente, registro, desenho, ciclo de vida...
    void Mostrar(void inicio(Tela&), void atualizar(Tela&), void renderizar(Tela&), bool console = false) {
        AoIniciar = inicio; AoAtualizar = atualizar; AoRenderizar = renderizar;
        hwnd = console ? initgraph(largura, altura, EW_SHOWCONSOLE) : initgraph(largura, altura);
        setbkcolor(corFundo); cleardevice();
        AoIniciar(*this);
        while (ativo && IsWindow(hwnd)) {
            inicioQuadro = GetTickCount();
            AoRenderizar(*this);
            BeginBatchDraw(); cleardevice(); RenderizarTodos(); EndBatchDraw();
            if (peekmessage(&mensagem)) { TransmitirEvento(&mensagem); mensagem = {}; }
            else while (!filaEventos.empty()) filaEventos.pop();
            AoAtualizar(*this);
            deltaTempo = GetTickCount() - inicioQuadro;
            if (tempoQuadro - deltaTempo > 0) Sleep(tempoQuadro - deltaTempo);
        }
        closegraph();
    }
    // Demais métodos (Encerrar, Ambiente, Registrar, Desenhar, etc.)
};

Componentes GUI exemplos

CaixaLinha (LineBox)

class CaixaLinha : public ComponenteGUI {
    Rect rect, rectAmpliado;
    COLORREF cor;
    bool ampliado = false;
public:
    void AoRenderizar() override {
        setlinecolor(cor);
        if (!ampliado) rectangle(rect.origem.x, rect.origem.y, rect.fim.x, rect.fim.y);
        else rectangle(rectAmpliado.origem.x, rectAmpliado.origem.y, rectAmpliado.fim.x, rectAmpliado.fim.y);
    }
    void AoEvento(ExMessage* msg) override {
        ampliado = (msg->message == WM_MOUSEMOVE && DentroRect(msg->x, msg->y, &rect));
    }
    CaixaLinha(Rect r, COLORREF c, int margem = 10) : rect(r), cor(c) {
        rectAmpliado = CriarRectPorCentro(rect.centro, rect.largura + margem, rect.altura + margem);
    }
};

Imagem (Image)

class Imagem : public ComponenteGUI {
    IMAGE img;
    bool corSolida = false;
    COLORREF cor;
public:
    Rect rect;
    void AoRenderizar() override {
        if (!corSolida) putimage(rect.origem.x, rect.origem.y, &img);
        else { setfillcolor(cor); fillrectangle(rect.origem.x, rect.origem.y, rect.fim.x, rect.fim.y); }
    }
    void AoEvento(ExMessage*) override {}
    Imagem(Rect r, std::string caminho) : rect(r) { loadimage(&img, caminho.c_str(), r.largura, r.altura); }
    Imagem(Rect r, COLORREF c) : rect(r), cor(c), corSolida(true) {}
};

Texto (Text)

class Texto : public ComponenteGUI {
    std::string estilo;
    COLORREF cor;
    RECT rectSistema;
public:
    Rect rect;
    std::string texto;
    bool centralizar = true;
    void AoRenderizar() override {
        setbkmode(TRANSPARENT);
        settextcolor(cor);
        settextstyle(16, 0, estilo.c_str());
        if (centralizar) drawtext(texto.c_str(), &rectSistema, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        else drawtext(texto.c_str(), &rectSistema, DT_VCENTER | DT_SINGLELINE);
    }
    void AoEvento(ExMessage*) override {}
    void DefinirTexto(std::string txt, std::string est = "宋体", COLORREF c = -1) {
        if (c != -1) cor = c;
        if (est != "宋体") estilo = est;
        texto = txt;
    }
    Texto(std::string txt, Rect r, std::string est = "宋体", COLORREF c = BLACK, bool centro = true)
        : texto(txt), rect(r), estilo(est), cor(c), centralizar(centro) {
        rectSistema = { r.origem.x, r.origem.y, r.fim.x, r.fim.y };
    }
};

Botao (Button)

class Botao : public ComponenteGUI {
    Imagem* img = nullptr;
    Texto* txt = nullptr;
    CaixaLinha* borda = nullptr;
    std::vector<std::function<void()>> aoClicar;
public:
    void AoRenderizar() override { if (img) img->AoRenderizar(); if (txt) txt->AoRenderizar(); if (borda) borda->AoRenderizar(); }
    void AoEvento(ExMessage* msg) override {
        if (img) img->AoEvento(msg);
        if (txt) txt->AoEvento(msg);
        if (borda) borda->AoEvento(msg);
        if (msg->message == WM_LBUTTONDOWN && img && DentroRect(msg->x, msg->y, &(img->rect)))
            for (auto& acao : aoClicar) acao();
    }
    void AdicionarAcao(std::function<void()> acao) { aoClicar.push_back(acao); }
    void RemoverAcao(int idx) { if (idx < (int)aoClicar.size()) aoClicar.erase(aoClicar.begin() + idx); }
    void LimparAcoes() { aoClicar.clear(); }
    Botao(Rect r, COLORREF corFundo, std::string txt, COLORREF corTexto, COLORREF corBorda)
        : img(new Imagem(r, corFundo)), txt(new Texto(txt, r, "宋体", corTexto, true)), borda(new CaixaLinha(r, corBorda)) {}
    ~Botao() { delete img; delete txt; delete borda; }
};

Grade (Gird)

class Grade : public ComponenteGUI {
    Texto*** celulas;
    Rect rect;
    COLORREF corLinha, corFonte;
    std::string estiloFonte;
public:
    int colunas, linhas;
    Rect tamCelula;
    void InicializarCelulas() {
        celulas = new Texto**[linhas];
        for (int i = 0; i < linhas; i++)
            celulas[i] = new Texto*[colunas]{nullptr};
        for (int y = 0; y < linhas; y++)
            for (int x = 0; x < colunas; x++)
                celulas[y][x] = new Texto("", DeslocarRect({rect.origem.x + x * tamCelula.largura, rect.origem.y + y * tamCelula.altura}, tamCelula), estiloFonte, corFonte);
    }
    Grade(Rect r, int cols, int rows, COLORREF cor = BLACK, std::string estilo = "宋体", COLORREF corTxt = BLACK)
        : rect(r), colunas(cols), linhas(rows), corLinha(cor), estiloFonte(estilo), corFonte(corTxt) {
        tamCelula = CriarRectPorPonto(0, 0, r.largura / cols, r.altura / rows);
        InicializarCelulas();
    }
    ~Grade() { /* desalocação similar ao original */ }
    void AoRenderizar() override {
        setlinecolor(corLinha);
        for (int x = 0; x <= colunas; x++) line(rect.origem.x + x * tamCelula.largura, rect.origem.y, rect.origem.x + x * tamCelula.largura, rect.fim.y);
        for (int y = 0; y <= linhas; y++) line(rect.origem.x, rect.origem.y + y * tamCelula.altura, rect.fim.x, rect.origem.y + y * tamCelula.altura);
        for (int y = 0; y < linhas; y++) for (int x = 0; x < colunas; x++) celulas[y][x]->AoRenderizar();
    }
    void AoEvento(ExMessage* msg) override {
        for (int y = 0; y < linhas; y++) for (int x = 0; x < colunas; x++) celulas[y][x]->AoEvento(msg);
    }
    void DefinirCelula(int linha, int coluna, std::string texto, COLORREF cor = BLACK) {
        celulas[linha][coluna]->DefinirTexto(texto);
    }
};

ListaGrade (GirdList)

Especialização da Grade para exibir listas paginadas de objetos.

Tags: easyx C++ gui nodegui VisualStudio

Publicado em 7-1 21:09