Mittwoch, 15. Juli 2015

OpenGL Game GUI / Widgets with Source Code (C++)

The GUI is intended for PC and/or Console Games. 
Focus was mostly on keeping everything simple, easy to use, fast, and flexible (like having skin and ttf support).
The performance is ~700 fps for 20 windows and 100 fps for 100 windows on a Notebook PC (NVidia GTX 765 / Core i7 2.4). 

The source (MIT license) can be downloaded from GitHUB or here



Usage examples:

Creating and opening a window: 

gui.window["mywin"]=Gui::Window("Hello World",100,100,350,250);

Adding a button:

gui.window["mywin"].button["Ok1"]=Gui::Button("OK",20,100,60,20);

Adding a callback to close the window when pushing the button:

gui.window["mywin"].button["Ok1"].callback_pressed=
  [](Gui::Window *w,Gui::Button* b,int index) { w->close(); };


Here two screenshots with different skins
( VS2012 / OpenGL / GLUT based / MIT license )

Features:
  • Advanced skin scaling: define how the inner and borders of a texture shall be scaled ( simple scale, repeating inner pattern or constant outer frame, carried out by 3 Shader types)
  • UTF8 Support
  • TTF Support
  • Easy skinning support
  • Alpha support (RGBA), so you can have transparent windows/widgets etc
  • direct access to all controls and their members
  • Non-blocking visualization
  • Callback support
  • Different from most GUIs, Widgets are stored directly inside the GUI rather having local variables pointing to instances of an overwritten class.
  • Directory: Controls are stored and accessed by a tree structure that is rendered every time like a scene graph
  • No event loop, show function or update function
  • No need to have a timer that calls an update function
  • Lightweight: Only Depends on GL and libFreetype and DevIL
  • Can easily be used in SDL, Qt and other environments. Just plug in the keyboard and mouse events and call the gui's draw function from your main render loop.
  • Config file support for default variables (font,padding etc) and skin textures+scaling parameters
  • Only 2000 lines of code for the entire Gui class including all widgets. Therefore easy to modify and extend.
  • Comes with a simple file-browser (See screenshot1)
  • MIT license
  • Multi-screen support (same as multiple Desktops). You can have a title screen, loading screen, ingame screen etc. Simply create them at the beginning and the switch between by setting gui.active_screen=number;
  • Window manager
  • Context menu
  • Custom mouse pointer
  • Controls have support for custom user variables like control.var.string["myvar"]="hello"
Available classes for controls:
  • Window class
  • Label class
  • Button class
  • Combo class
  • Radio class
  • Tab class
  • Textedit class
  • Slider class
  • CheckBox class
  • Menu class
What the GUI is good for:
  • PC/Console Games
  • Simple menus
What the GUI might not be suited for
  • Complex GUIs that need tree-views , docking ,drag & drop etc
  • Mobile Devices ; perhaps too slow (needs testing) and also the render code needs to become OpenGL ES
Current Limitations (might be solved in the future)
  • Uses old OpenGL (glMatrix etc)
  • Usually one or two render calls per widget rather than one opengl render call for the entire gui.
  • 2D windows/widgets only. You could overwrite the render shader to get 3D, but you would have to map 2D mouse coordinates to 3D then by yourself. 
  • No animations (you could create them yourself by updating the textures accordingly or modifying the shader that render the widgets)
  • Only one font size and one font type
  • The GUI class is a singleton
  • The TextEdit does not support arbitrary cursor position or ctrl+c/v/x operations
  • Drag and Drop is not supported
  • No automatic window layout
  • No docking
  • No Ressource files (yet)
  • No treeview class
  • No caching of rendered labels, geometry, etc. Therefore every frame, the entire tree is traversed and every single character of a rendered text is processed for rendering.
The usage is also described in the previous post.

The code used to create window + widgets + menu in screenshot2  is the following:

 gui.init( Gui::Flags::CONTEXT_MENU | Gui::Flags::CUSTOM_MOUSE ,
    "../data/gui_global.txt" , 
    "../data/gui_skin.txt");

 // Main Window

 Gui::Window w=Gui::Window("Hello World",100,100,350,250); 

 // Add Simple Label to Window

 w.label["l"]=Gui::Label("Label",20,70,100,20);

 // Add Simple Button to Window

 w.button["Ok1"]=Gui::Button("OK",20,100,60,20);
 w.button["Ok1"].callback_pressed=
  [](Gui::Window *window,Gui::Button* control,int index)
  {
   window->close();
  };

 w.button["Ok2"]=Gui::Button("",220,100,100,100);
 w.button["Ok2"].skin=Skin( "../data/smiley.png",
        "../data/smileybw.png",
        "../data/smiley2.png");

 // Add Simple Combo to Window

 w.combo ["cb"]=Gui::Combo(120,100,60,20);
 w.combo ["cb"].add_item("test");
 w.combo ["cb"].add_item(L"東京");
 w.combo ["cb"].callback_selected=
  [](Gui::Window *w,Gui::Button* control,int index) // text entered callback example
  {
   Gui::Combo &c=*(Gui::Combo*)control;
   w->label["l"].text=Gui::String( c.selected ) + "  selected";
   w->label["l"].textcolor=vec4f(1,0,0,1);
  };

 // Add Text Edit to Window

 w.textedit["txt"]=Gui::TextEdit(10,"text",20,150,160,20);
 w.textedit["txt"].callback_text_entered=  
  [](Gui::Window *w,Gui::Button* control,int index) // text entered callback example
  {
   Gui::TextEdit &t=*(Gui::TextEdit*)control;
   w->title.text=t.text;
  };

 // Add Radio

 w.radio["rad"]=Gui::Radio(20,190,20,20); // first button
 w.radio["rad"].add_item(50,190);   // second button
 w.radio["rad"].add_item(80,190);   // third button
 w.radio["rad"].callback_pressed=
  [](Gui::Window *w,Gui::Button* control,int index)
  { 
   w->x+=10;
  };

 // Add Slider

 w.slider["s1"]=Gui::Slider( 0,200,100,   /* min,max,default*/ 
        20,220,160,20); /* window x,y,sx,sy */ 
 w.slider["s1"].callback_pressed=
 [](Gui::Window *w,Gui::Button* control,int index)
 {
  Gui::Slider &b=*((Gui::Slider*) control); 
  w->button["Ok1"].text=Gui::String(int(b.val)); 
 };

 w.slider["s2"]=Gui::Slider( 0,200,100,   /* min,max,default*/ 
        350,100,20,160, /* window x,y,sx,sy */
        Gui::Slider::VERTICAL );

 // Add File Menu to Window

 Gui::Menu m=Gui::Menu("File",/* x,y,sx,sy */ 9,39,50,20, /* menuwidth */ 100);
 m.add_item("Load",
  [](Gui::Window *window,Gui::Button* control,int index) // menu button callback
  {
   gui.screen[0].window["filebrowser"]=  // open file dialog
    gui_file_dialog( "Load SaveGame" , "Load" , "Cancel" ,
     /*get_current_dir()*/ "..\\data\\win8",".png", 100,100,

     // file dialog ok button callback
     [](Gui::Window *w,Gui::Button* b,int index) 
     {  
      MessageBoxA(0, 
       w->textedit["Filename"].text.c_str() , 
       w->label["dir"].text.c_str() ,0);  
      w->close();    
     }
   );
  });

 m.add_item("Close",
  [](Gui::Window *w,Gui::Button* control,int index) // menu button callback
  {
   w->parent->close(); // close window
  });

 m.add_menu("submenu");
 m.window.menu[0].add_item("test1");
 m.window.menu[0].add_item("test2");

 w.menu["menu"]=m;

 // Add new Window to Screen 0 (default)

 gui.screen[0].window["hello1"]=w; // finally put window on the screen (1st copy)

 // Modify and Add new Window to Screen 0 (default)

 w.move(500,100);
 w.resize(400,300);
 w.minsize(150,150);

 gui.screen[0].window["hello2"]=w; // finally put window on the screen (2nd copy)

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

 // also use our previous menu as context menu and file menu
 // Note : "context_menu" is reserved for the context menu
 //    all other id names are common menus on the background

 gui.screen[0].menu["context_menu"]=m;

 gui.screen[0].menu["file"]=m;
 gui.screen[0].menu["file"].y=5;

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

 // Add Button to Background

 gui.dialog["sample"]=w; // store for later use in the callback

 gui.screen[0].button["more"]=Gui::Button("More Windows Plz!!",50,50,200,20);
 gui.screen[0].button["more"].callback_pressed=
  [](Gui::Window *w,Gui::Button* control,int index) // menu button callback
  {
   int id=gui.screen[0].window.add(gui.dialog["sample"]);
   gui.screen[0].window[id].x= timeGetTime() % (int)gui.screen_resolution_x;
   gui.screen[0].window[id].y= timeGetTime() % (int)gui.screen_resolution_y;
  };

 // Add Tabbed Window to Background

 Gui::Tab t=Gui::Tab("Win1",350,150,300,200,50,20);;
 t.add_tab("Win2");
 t.add_tab("Win3");
 t.add_tab("Win4");
 loopi(0,4)
 {
  t.window[i].button.add(Gui::Button("OK",20+i*10,20,60));
  t.window[i].label["lab"] =Gui::Label("some text",20+i*20,90,100);
  t.window[i].button["test2"]=Gui::Button("OK",100+i*10,20,60);
  t.window[i].button["test3"]=Gui::Button("OK",50+i*10,50,60);
 }
 t.flags=Gui::Tab::MOVABLE; // Make it movable
 gui.screen[0].tab["mytab"]=t;



Main reason for the development of the GUI was, that after trying several gui's more or less suited for games (LibRocket, Qt, NVWidgets, CEgui, etc), I found that none of them was really light weight, easy to use and fully skinable at the same time. So the solution was to create one which is exactly so. 




2 Kommentare:

  1. Its using GL 1.1 but you can use GL4 features like Tessellation since Glew supports these functions

    AntwortenLöschen