/Gempyre

Gempyre UI Framework for C++ (and Python)

Primary LanguageC++MIT LicenseMIT

wqe

Gempyre

UI Framework

Gempyre is a UI multiplatform framework. Supporting Windows, Mac OSX, Linux, Raspberry OS and Android. Gempyre is minimalistic and simple; It is a UI framework without widgets - instead, the UI is composed using common web tools and frameworks. Therefore Gempyre is small, easy to learn and quick to take in use.

For the application, Gempyre let engine to be implemented using C++ (or Python), the UI can be constructed using CSS and HTML like any front end. All common tools from the web technologies are be available. Gempyre library provides a simple and easy C++ API for a application development and the whole API is only a few dozen calls.

Gempyre is intended for applications that has a solid C++ core (or C), and allows rapid UI development without extra hassle with platform specific UI development.

Gempyre is multiplatform, its core is written using C++17 (tested on OSX (CLang), Ubuntu (gcc), Raspberry OS (gcc) and Windows 10 (MSVC and MinGW) ). The Gempyre framework can be downloaded at Github under MIT license.

Gempyre itself does not contain an application window. The UI is drawn using external application. Some OSes defaults to system browser, some Python webview. However that is fully configurable per application.

Gempyre is a library that is linked with the application, except for Android, see Gempyre-Android. For Python, install Gempyre-Python on top of Gempyre library.

Gempyre API

  • gempyre.h, core classes for the application development.
  • gempyre_utils.h, miscellaneous collection of helper functions. These are not available for Python as there are plenty of analogous fuctionality.
  • gempyre_graphics.h, HTML canvas functions for bitmap and vector graphics.
  • gempyre_client.h, file dialogs for Gempyre application. Not available when system browser is used as an application window.

Gempyre Documentation

Linux

  • Run
    linux_install.sh

Mac OSX

  • Run
     osx_install.sh

Windows

MSVC

MinGW

Raspberry OS

  • Requires Rasberry OS Bullseye (older is ok, but you need more recent gcc in order to build C++17). Tested Raspberry Pi 3 and Raspberry Pi 4.
  • Quite late CMake is required, here are snap instructions.
  • Run
      pi@raspberrypi:~/Development/Gempyre ./raspberry_install.sh
  • When building on Raspberry, please pass -DRASPBERRY=1 for your cmake call. E.g.
       pi@raspberrypi:~/Development/Tilze/build $ cmake .. -DRASPBERRY=1

FAQ

Q: After installation you get: "WARNING: webview is not installed -> verification is not completed!", what is that?
A: Most likely python3 webview is not installed. See installation from pywebview. Please also make sure websockets python library is installed.

$ pip3 install pywebview && pip3 install websockets

The error is not fatal, but as a consequence the default UI is not drawn on its own window and it fallbacks to the default browser.

Q: How to use some HTML/JS/CSS feature for GUI from C++ that does not have a API?
A: Try Ui::eval(), it let you execute javascript in the gui context: e.g. Here I change a checkbox element so it fires a change event.

ui.eval("document.getElementById(\"" + check_box.id() + "\").click();");

Q: Why Canvas drawings seems to happen in random order?
A: Canvas drawing is highly asynchronous operation and subsequent CanvasElement::draw() operations may not get updated in the order you have called them. The solution is either make a single draw call where all items are updated once, or use CanvasElement::draw_completed() to wait until each draw has happen. CanvaElement::draw_completed() should be used for animations (or game like frequent updates) instead of timers for updating the graphics. Like this example

Q: Why Gempyre::Bitmap merge is not working?
A: You are likely mergin on uninitialized bitmap. Gempyre::Bitmap(width, height) or Gempyre::Bitmap::create(width, height) does not initialize bitmap data, therefore it's alpha channel can be zero and bitmap is not drawn. To initialize the bitmap, use Gempyre::Bitmap::Bitmap(0, 0, width, height, Gempyre::Color::White), or draw a rectangle to fill the Bitmap, after the construction.

Example

Hello world

As an example, here is a simple Gempyre application with a single button. The UI is written using HTML; please note the <script type="text/javascript" src="gempyre.js"> </script> line is required for every Gempyre application. The HTML widgets are accessed by their HTML element ids from the C++.

cmake_minimum_required(VERSION 3.26)

project( hello VERSION 1.0 LANGUAGES CXX)

add_executable(${PROJECT_NAME} main.cpp)

# Find Gempyre after installation 
find_package(Gempyre REQUIRED)

# Add gempyre_add_resources
include(gempyre)

gempyre_add_resources(PROJECT ${PROJECT_NAME}
    TARGET include/hello_resources.h 
    SOURCES gui/hello.html)


set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
target_include_directories(${PROJECT_NAME} PRIVATE . include)
target_link_libraries(${PROJECT_NAME} gempyre::gempyre)
 <!doctype html>

<html lang="en">

    <head>

        <meta charset="utf-8">

        <title>Hello</title>

    </head>

    <body>

        <script type="text/javascript" src="gempyre.js"></script>

        <button id="startbutton">...</button>

        <div id="content"></div>

    </body>

</html>

And then we have a main.cpp. I discuss here every line by line. At first, gempyre.h is included so it can be used.

#include  <gempyre.h>

Within Gempyre you normally build in the HTML and other resources, to compose a single file executable. It is preferred that Gempyre is statically linked in, thus the application is just a single file. Therefore distributing and executing Gempyre applications shall be very easy: just run it! There are no runtimes to install nor DLLs to be dependent on; just a single binary executable.

The resource composing is done in CMakeLists.txt (single line), yet here the generated header is included, it contains a ‘Hellohtml’ std::string object that will be passed then to the Gempyre::Ui constructor.

#include "hello_resources.h"

int main(int /*argc*/, char** /*argv*/)  {

In the constructor, you provide a mapping between file names and generated data and the HTML page that will be shown.

  Gempyre::Ui ui({ {"/hello.html", Hellohtml} }, "hello.html");

HTML elements are represented by Gempyre::Element. The element constructor takes a HTML id to refer to the corresponding element in the UI. Here we refer to the text area and button as defined in the HTML code above. The Gempyre::Element represents any of the HTML elements. It's only inherited class is Gempyre::CanvasElement that implements specific graphics functionalities.

 Gempyre::Element text(ui, "content");

 Gempyre::Element button(ui, "startbutton");

The Gempyre API provides a set of methods to get and set HTML content, values, and attributes. Here we just use setHTML to apply a given string as an element HTML content.

   button.set_html("Hello?");

The subscribe method listens for element events, and when the button is "clicked" a given function is executed.

button.subscribe("click", [&text]() {

     text.set_html("Hello World!");

   });

The Gempyre::Ui::run() starts an event loop. In this example, the system default web browser is opened, and the UI is executed on tab (there are more alternatives to have a system looking application) and the application is waiting for the button to be pressed or the browser window to be closed.

  ui.run();

  return 0;

}

Selection list

Dynamic selection list (or combo box) is as easy as adding an empty select element in the Ui HTML code:

<select name="levels" id="level_select">
</select>

and then fill it in cpp side:

Gempyre::Element select{ui, "level_select"};
for(const std::string& level : wp.levels()) {
   Gempyre::Element opt{ui, "option", select};
   opt.set_attribute("value", level);
   opt.set_html(level);
   }

to read a value upon change you subscribe it's change:

select.subscribe(Gempyre::Event::CHANGE, [](const auto& e) {
    std::cout << e.properties.at("value") << std::endl;
}, {"value"});

Applying an initial value is trivial:

select.set_attribute("value", wp.levels[level_index]); // set value to some other than 1st

Please note that as each event has a lot of properties, you have to list what you need. For a selection change (as well as most of the inputs) a value is used.

Some example projects using Gempyre

Some future development directions

  • Binary releases (Maybe installer / some packet manager support / pip)
  • Support for secure web socket (nice for remote UIs)
  • Testing coverage and perf measurements.
  • JS testing

maybe not

  • Flatbuffer/protobuf instead of JSON
  • Using WASM instead of JS
  • POC of Gempyre-Android style architecture also in core.git

Late updates

2021 1

  • Proper build and install with cmake
  • Use GTest for API testing
  • Rewrote timers + other smaller fixes

2021 2

2022 1

  • Lot of fixes
  • CI using Github actions

2022 2

  • Lot of fixes
  • Raspberry, and improved MinGW support

2023 1

  • Lot of fixed
  • API changes and harmonize with GemGui-rs
  • Testing coverage

2023 2

  • Python UI fixes
  • Performance update
  • Refactoring internals
  • Ready to change uwebsockets to libwebsockets or websockets++. (however not completed)
  • Pedantic and Sanitizers
  • Suppress external library warnings (except MSVC not working, it seems :-/ )
  • Update / improve documentation

Copyright Markus Mertama 2020, 2021, 2022, 2023