Author Topic: GUI with gauges in python  (Read 974 times)

0 Members and 1 Guest are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18089
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
GUI with gauges in python
« on: November 10, 2024, 03:29:56 pm »
So time has come that I have a little some here and there to play.

I'm looking at Python for better or worse to create an application on a single board computer to run on a small screen. I have found that TKinter is the default graphics thingy and OK. It is of course old fashioned. and there are no dial or gauge type of widget. I see people have created their own libraries for TKinter to make these. Is this a sensible way to go or am I just starting on the wrong foot?

I have read very little about tkinter so I don't know how easy it is to create my own stuff with it.
 

Offline jfiresto

  • Frequent Contributor
  • **
  • Posts: 886
  • Country: de
Re: GUI with gauges in python
« Reply #1 on: November 10, 2024, 04:30:21 pm »
So time has come that I have a little some here and there to play.

I'm looking at Python for better or worse to create an application on a single board computer to run on a small screen. I have found that TKinter is the default graphics thingy and OK. It is of course old fashioned. and there are no dial or gauge type of widget. I see people have created their own libraries for TKinter to make these. Is this a sensible way to go or am I just starting on the wrong foot?

Been there, done that. Tkinter is fast running, but can be low level, and a lot of work if you have to work around not having a mouse or an equivalent. The other issue is that it uses callbacks, but you can hide them all with a little thought (it took me about a day's worth).
-John
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18089
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: GUI with gauges in python
« Reply #2 on: November 10, 2024, 04:32:50 pm »
why is a callback an issue?
 

Offline globoy

  • Frequent Contributor
  • **
  • Posts: 255
  • Country: us
Re: GUI with gauges in python
« Reply #3 on: November 10, 2024, 05:03:56 pm »
I have only done the equivalent of a "hello world" with it, but LVGL has a micropython binding.  LVGL has gauges, and a lot of other nice UI capabilities including anti-aliasing.

https://github.com/lvgl/lv_binding_micropython

I know you're looking for a python solution and there have been discussions about how to make it run there too but I'm not sure where they are currently.

https://forum.lvgl.io/t/use-lvgl-with-python3/10686
 

Offline jfiresto

  • Frequent Contributor
  • **
  • Posts: 886
  • Country: de
Re: GUI with gauges in python
« Reply #4 on: November 10, 2024, 05:19:28 pm »
why is a callback an issue?
A callback isn't, nested callbacks, once deep enough, are.
-John
 

Offline mariush

  • Super Contributor
  • ***
  • Posts: 5160
  • Country: ro
  • .
Re: GUI with gauges in python
« Reply #5 on: November 10, 2024, 05:44:23 pm »
« Last Edit: November 10, 2024, 05:46:34 pm by mariush »
 

Online kripton2035

  • Super Contributor
  • ***
  • Posts: 2698
  • Country: fr
    • kripton2035 schematics repository
Re: GUI with gauges in python
« Reply #6 on: November 10, 2024, 06:19:07 pm »
is there any IDE where you can define windows and buttons and menus and all with a mouse and not with dozens of lines of text ?
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18089
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: GUI with gauges in python
« Reply #7 on: November 10, 2024, 06:21:30 pm »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18089
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: GUI with gauges in python
« Reply #8 on: November 10, 2024, 06:24:50 pm »
I have only done the equivalent of a "hello world" with it, but LVGL has a micropython binding.  LVGL has gauges, and a lot of other nice UI capabilities including anti-aliasing.

https://github.com/lvgl/lv_binding_micropython

I know you're looking for a python solution and there have been discussions about how to make it run there too but I'm not sure where they are currently.

https://forum.lvgl.io/t/use-lvgl-with-python3/10686

I would love to use lvgl, that is what the current protype is done in but it was done on an ESP32 (because someone had a solution looking for a problem). I want to redo it on linux. I suppose that as python is simply a bunch of wrappers for all that is already out there I may as well look at C++ again and using LVGL that way.
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 3984
  • Country: us
Re: GUI with gauges in python
« Reply #9 on: November 10, 2024, 11:23:17 pm »
is there any IDE where you can define windows and buttons and menus and all with a mouse and not with dozens of lines of text ?

There are lots of tool that will let you lay out a bunch of widgets graphically.  You of course still have to write the event handlers and update routines.  I have used QT designer (I think it goes by a different name now, it's been ages).  Basically you would drag and drop the widgets into place, and then each would have a properties dialog where you could select how events mapped to functions in your code.  There are similar design tools for other graphical toolkits, but that's the only reasonably mainstream one I have used.  Getting it to look "professional" and especially to get it to resize well is still a fair bit of work.  Also, you can't easily incorporate dynamically generated / enabled UI elements in a straightforward way.  But it's definitely a tool that can get a simple but functional UI up and running in as little as an hour or so.
 
The following users thanked this post: kripton2035

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7126
  • Country: fi
    • My home page and email address
Re: GUI with gauges in python
« Reply #10 on: November 11, 2024, 03:36:42 am »
is there any IDE where you can define windows and buttons and menus and all with a mouse and not with dozens of lines of text ?
For Qt, you use Qt Designer (for Qt 5 or Qt 6).  You can run Qt on any OS and desktop environment, including Windows, as long as you also have Qt installed.

For GTK+, you use Glade.  Don't worry, you're not required to use GNOME desktop with GTK+; any desktop environment will work, as long as you have GTK+ installed.

There are two options: Either you let the editor generate the code for you, or you save the UI description as an XML file (.ui, for both cases, although the syntax is different).  I recommend the latter, so that you can keep modifying it afterwards without any changes to your code.  It is slightly more complicated to implement at first, but reduces maintenance cost/complexity.

You can find a visual list of Qt 5 widgets here, Qt 6 widgets here, GTK+3 widgets here, and GTK+4 widgets here.

Both also support graphics area widgets, which are basically canvases.  You write a draw function, that redraws the entire widget from scratch whenever called.  Qt/GTK+ will call it whenever necessary.  For continuous dial updates, you can use animation features provided by the toolkit, and/or add a timeout that triggers a redraw.  (Basically, with the animation support, the toolkit will tell you the time elapsed since the last redraw, if you need that for e.g. decay effects when update frequency varies.)  They have a complete set of drawing functions, including all sorts of transformations you might need.
 
The following users thanked this post: kripton2035

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7126
  • Country: fi
    • My home page and email address
Re: GUI with gauges in python
« Reply #11 on: November 11, 2024, 05:34:24 am »
Apologies for the wall of text, but if you want to do Python UIs in Linux, especially for microcontrollers or network widgets, you might want to read this.

I'm looking at Python for better or worse to create an application on a single board computer to run on a small screen.
I'd use Qt5.  If you can draw an image of what kind of widgets you want, I can write a Python 3 example for you.

I like to use Inkscape for these (but note that you'll need to upload the resulting SVG file somewhere else than here; I use my own webhost), with the following Document Properties set in the following order:
  • Format: mm
  • Width: display width in mm
  • Height: display height in mm
  • Viewbox:
    • X: 0
    • Y: 0
    • Width: display width in pixels
    • Height: display height in pixels
  • Scale: (autocalculated as the pixel pitch in mm)
You can set the Display units to whatever units you prefer to work with.  I also often set up one or more Rectangular grid in the Grids tab in Document Properties, for easier alignment of things.  The # key will show/hide the grid.  The % key controls the global snapping to grid, but you have finer control using the ▸ icon next to the magnet at top right corner of the Inkscape window.  Advanced mode lets you select exactly what snaps to grid or guides; I use it heavily.

This way, whatever you design, you can immediately print and look at it as if it was the physical display, exact correct size and all.  If your display settings are correct, opening the SVG image in Inkscape at 100% zoom, or your browser, should show it in correct size as well.  As it is a vector image format, the fidelity is optimal at all scales.

(Also makes an excellent medium for mockups for remote clients.  If one also has the time to learn HTML + CSS + Javascript, the SVG images can be converted to HTML pages –– like my home page is, it is a single self-contained 21,749-byte HTML file with no other files or dependencies ––, and made interactive –– like my FIR spectrum page; just stuff the filter coefficients like 1 0 -2 0 1 for a bandpass filter or 1 1 for a low-pass filter in the top right rectangle, and it will draw the filter spectral response for you, also in a single self-contained HTML file that works in a browser even locally without any internet connection.)

You can choose either PySide2 or PyQt5 bindings for Python 3.  If you use
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.uic import loadUi
or
    from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools
    def loadUi(uifile):
        return QtUiTools.QUiLoader().load(uifile)
they will be identical for practical purposes.  loadUi is used to load XML .ui files, including those used in Qt Designer.  If you do not use .ui files, then from toolkit import QtCore, QtGui, QtWidgets suffices.

As I love to use SVG files, I also add QtSvg to the import list.

The namespacing is such that QtWidgets and QtGui classes and constants will always need the corresponding QtWidgets. or QtGui. prefix, and Qt core constants and such a QtCore. prefix.  This can be a bit annoying at first, because you will forget, and get lots of errors, but remembering it is in most cases this namespacing issue, will help.  Some constants are specific to that class, so you'll need to prefix the entire class name.  For example, the QGraphicsScene widget (for drawing your own dials and complex graphics to) mentions QPen and QBrush; you need to remember that here, you need to refer to them as QtGui.QPen and QtGui.QBrush instead.

If you wanted to display say nice RPM and speed gauges, you can use a JPEG or PNG image (or SVG image, if you use QtSvg) as the background for your own widgets.

For providing the dial/gauge inputs, I like to use USB serial.  I never use Qt USB or Serial abstractions, or libusb variants, because they just do not perform sufficiently well in my opinion.  For Python, I use the built-in termios library for access –– only works in Linux, Android, macOS, BSDs; not in Windows.  The serial device name is either fixed or passed as a command-line parameter, and in Linux an udev rule is needed to allow access and to set up a freely-nameable symlink.  It's just a line or two of text and easy to set up.

The serial communication will be run in a separate thread (two, if you use bidirectional communications with the USB device), using QtCore.QThread.  Whenever a thread receives a new status change, for example new potentiometer state, it uses the Qt signal mechanism to trigger an update.  Whenever the UI wants to cause something to happen, maybe control a LED, it uses the Qt signal mechanism to trigger the sender thread to send the command.  This also means that for interactive buttons, like one that stays "pressed" while the LED is on, you have a signal slot for LED became on/off (or separate slots, if you prefer), and when the user clicks on the button, only the signal is sent (and the visual state of the button is not changed), and only when the signal is received on the slot, is the visual button status updated.

This way, the UI will reflect the external device status, and not "lie" to the user.  I think an user interface where clicking a button appears to work immediately, but there is a human-detectable interval between that and the physical event.  I'd rather have the interface reflect the physical situation, with the button press/release visually showing when the thing has occurred.

Also, the serial communications is then not limited to query-response, but can have multiple concurrent queries in flight at the same time.  It does mean each query should have an identifier (an increasing number, like Gcode line numbers), which is included in the response.  It does not matter to me at all whether the communications are text or binary; Python built-in struct library makes it trivial to adjust.

If you happen to have Teensy 4.0 or Teensy 4.1 microcontrollers, then making an experimental/fake version of the USB inputs is trivial in the Arduino environment.  You simply select Serial from Tools > USB Type menu, and use Serial.write() to send the data.  If you call that for arrays/blocks of data of at least 16 bytes or so, you can get 25 Mbytes/sec (200+ Mbits/s) data rate, in one direction.  If you have soldered pins on it, stick it on a breadboard, and connect some 10k linear potentiometers and LEDs with 1k resistors (1.3mA at Vf=2.0V), and with just a dozen or two lines, you're go.

Network communications, like a dashboard for network gadgets, is very similar, using Python built-in socket library.  (It is available even in Windows; it is only not available in WebAssembly (WASI) environments).  For IP/UDP -based protocols, a single thread can handle both incoming and outgoing traffic.  For TCP/IP, I like to use separate threads if possible, because certain TCP operations are blocking at the kernel level, and unless signal delivery based timeout mechanism is used, can lead to DDOS vulnerabilities if connecting to nefarious peers/services.  If you trust the other ends, then a single thread suffices, even for multiple connections.)

Because Python is easy to interface to native dynamic libraries via the ctypes library, I recommend using it to implement any proprietary stuff.  It is also excellent for implementing TCP/IP services to many clients, with the "actions" implemented in Python.  The API to the library will be visible, but does not need to be licensed the same as the Python code.  In particular, if you use LGPL'd Qt modules (say, QtCore, QtWidgets, QtGui, QtSvg), you can use any license for the Python code, and any license for the native dynamic libraries – which do not need to be open sourced at all.  In my opinion, this approach gets the best of all worlds: the users can tweak the UI if they ever need to, developer gets to keep the Secret Sauce proprietary, and Qt is used according to the licenses.  If you also refer to Qt use (via LGPL) and promote it in the documentation, both Qt Project and the Qt Group (formerly Qt Company) will be happy with you.

It is possible to compile a version of Qt to use EGLFS Qt Platform Abstraction (qeglfs for Qt 5, for Qt 6), which lets a single application to run fullscreen with OpenGL ES 2.0 acceleration on bare Linux, without X/Wayland et cetera.  This is licensed under a number of different licenses, including LGPL.  Technically, this is called Qt for Embedded Linux.  Note the Linux in there.  The main work to do this is in building all the parts for your target architecture correctly; plus to keep everyone happy, if you distribute the code/binaries, providing the exact sources you used is necessary.  I personally would also describe step-by-step how to rebuild the libraries in the documentation, both to keep developers happy, but also as a safety net against overeager lawyers.  (With that information and showing they can be used to rebuild identical binaries, any lawsuit wrt. LGPL that is about Qt and not LGPL, would get summary judgment in most jurisdictions.  But neither current Qt developers or Qt Group/Company would bring such a lawsuit in the first place.)

Qt Embedded is related, but a larger set of mostly proprietary Qt modules for which you need a license from the Qt Group.  You can see the basic frame license agreement terms and conditions here.  It includes a lot of useful stuff beyond what I described above, but is not required for any of the stuff I've described above.  So, for small developers and prototype design is likely not that useful, only becoming useful when your sales and development cost warrant the added usefulness.
 
The following users thanked this post: kripton2035

Online Smokey

  • Super Contributor
  • ***
  • Posts: 2979
  • Country: us
  • Not An Expert
Re: GUI with gauges in python
« Reply #12 on: November 11, 2024, 06:48:00 am »
For simple, single window, and internal use GUIs with python I use tkinter.  It's built in and low resource, which is nice.
This is a cool trick.  Using actual language, describe in as much detail as you can exactly what you want your GUI to look like and where you want all the interactive elements.  Also give names to anything you want named specific things.  Dump that all into ChatGPT as a prompt and it will do all the boiler plate tkinter for you. 
The new Claude is pretty good at this too.  It will even render the thing and show you what the GUI will look like if you ask it.

While I've used QT with python before for a more involved application, I'm pretty sure I won't do that again. 
« Last Edit: November 11, 2024, 06:49:56 am by Smokey »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7126
  • Country: fi
    • My home page and email address
Re: GUI with gauges in python
« Reply #13 on: November 11, 2024, 10:50:29 am »
Here is a tiny dialog example I wrote for this thread in March 2024, slightly edited.

First, you create an .ui file, say example.ui, attached as example.ui.txt.  You can open it in Qt Creator, and modify it as you like. 

For ease of use, name your interactive widgets so that in the Python code can automatically implement handlers for the corresponding events (signals in Qt parlance) by naming the handler on_widgetName_signal.

With a fixed-size display, make it a fullscreen window of the correct size, and you don't need to worry about the intricacies of scaling.  This example does not set the scaling properties, so if you resize the window, the widgets will stay in its upper left corner.  For proper scaling (so widgets will expand to the available space), you need to use a geometry manager, some subclass of QLayout; I typically use QFormLayout for stuff like this dialog, and QGridLayout for everything else.

The corresponding Python code for the example dialog is
Code: [Select]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: CC0-1.0
#
# This is in public domain.  Written by Nominal Animal, 2024.
#

import sys

try:
    # Prefer PyQt5,
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.uic import loadUi
except ImportError as err:
    # but fall back on PySide2.
    from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools
    def loadUi(uifile):
        return QtUiTools.QUiLoader().load(uifile)

class ExampleDialog(object):

    def __init__(self):
        super().__init__()

        # Load UI,
        self.ui = loadUi("example.ui")
        # and connect on_<widget>_<signal> to corresponding members as handlers:
        for slot in dir(self):
            if slot.startswith("on_") and slot.find("_", 3) > 3:
                method = getattr(self, slot)
                widgetName, signalName = slot[3:].rsplit("_", 1)
                source = self.ui.findChild(QtCore.QObject, widgetName)
                signal = getattr(source, signalName, None)
                if signal:
                    signal.connect(lambda *args, **kwargs: method(source, *args, **kwargs))

        # Display this ui.
        self.show = self.ui.show

    # Example handler
    def on_radioButton_3_USB_clicked(self, widget, clicked):
        print("clicked on %s" % widget.objectName())

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    # Set these.  Among other things, QtCore.QSettings(scope) uses these.
    app.setOrganizationName('The Name of Your Organization')
    app.setApplicationName('The Name of This Application')

    win = ExampleDialog()
    win.show()
    status = app.exec_()

    if win.ui.result() == QtWidgets.QDialog.Accepted:
        if win.ui.findChild(QtWidgets.QRadioButton, "radioButton_1_LAN").isChecked():
            print("LAN: %s" % win.ui.findChild(QtWidgets.QLineEdit, "lineEdit_IP").text())
        elif win.ui.findChild(QtWidgets.QRadioButton, "radioButton_2_COM").isChecked():
            print("COM: %s" % win.ui.findChild(QtWidgets.QLineEdit, "lineEdit_IP_2").text())
        elif win.ui.findChild(QtWidgets.QRadioButton, "radioButton_3_USB").isChecked():
            print("USB: %s" % win.ui.findChild(QtWidgets.QLineEdit, "lineEdit_IP_3").text())
        else:
            print("None selected")
    else:
        print("Canceled")

    del win
    del app
    sys.exit(status)
The first try .. except clause uses PyQt5 bindings if available, and falls back to PySide2 otherwise, in a way that makes the two practically identical to our Python code.  If you wanted to control the bindings using environment variable, that part expands by about 20 lines, where an environment variable, typically QT_API.  Instead of this, you can use QtPy instead, but I dislike the added abstraction and maintenance load of yet another dependency.

Like all widget toolkits, but unlike SDL, your program releases all control to the widget toolkit, and then the toolkit will trigger events and call your code.  You can implement an idle handler, which will be called whenever all pending events have been processed, and you can implement timeouts that cause an event to be generated in the future, for animations and such.

In Qt, for graphical UI stuff, you first instantiate a QtWidgets.QApplication class.  (Code that does not use any QtWidgets can use QtGui.QGuiApplication, and code that runs in batch mode without any graphics can use QtCore.QCoreApplication.)

This object then represents the application itself, and owns all windows you create.  There are some nifty tricks there for multidocument handling, but most importantly, you use the app.postEvent() to send events to your widgets from other threads.  You can also use app.setStyle() to set the application look and feel (see QtWidgets.QStyle).

(If you use threads, do not subclass subclass QtCore.QThread, but use the plain QtCore.QThread.  In its .run() method, instantiate your own subclass of QtCore.QObject for the communication with other threads.  This way, Qt will handle the signal passing between threads correctly, with each thread owning the Qt widget or object running the event code.)

Qt provides a way to get and set settings objects in both system (machine) and user scopes, via
    systemSettings = QtCore.QSettings(QtCore.QSettings.SystemScope)
    userSettings = QtCore.QSettings(QtCore.QSettings.UserScope)
To set a value, use the .setValue(name,value) method; name is always a string, but value can be of any type.  Conversely, .value(name) or .value(name,default) method to access them.  These are automatically loaded and saved for you.  You can also use the .sync() method to save the contents, but I don't bother.  Note that your constructors (__init__() methods) and other functions can access the settings this same way.  To find out if there have been any errors, check the .status() method.  If everything is okay, it should be equal to QtCore.QSettings.NoError.

Next, you instantiate your windows.  They do not need to become instantly visible, as they by default stay hidden until you call .show() on them or their parents.  In Python, the __init__() method is the constructor, called whenever the class is instantiated.  The super().__init__() is the Python 3 way of calling the __init__() method of the parent type first, so you almost always have it first in your subclass constructors.  If there are parameters that can be passed to the constructor, you can use form
    class myClassName(extendsClass):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
where args is the list of positional parameters (and *args expands that list in place for function parameters, much like varargs in C), and kwargs is a dictionary of named parameters (and **kwargs expands that for function parameters).

Because I load the entire graphical user interface from an .ui file, my main window class is not actually an Qt object, but a plain Python one.  Its .ui member will have the widget hierarchy.  I prefer this approach, because this way I will have no collisions with the Qt instance or class methods and properties.

When the .exec_() method of the application object is called, control passes to Qt.  This call returns only when the application is closing.  The return value is usually 0, but if one uses application.exit(value) or similar methods, that value is returned.

In this dialog example, the main program then calls the dialog window .result() function, to see whether the selection was accepted or canceled.  (Closing the window is the same as canceling.)

Finally, I make it easier for the Python garbage controller by deleting the main window object first, then the application object.  If you passed a reference to the application object to your window hierarchy, I recommend you set it to None before deleting the main window object.  This ensures that the various destructors get called, and in the correct order.  sys.exit(value) passes the status code, zero being No Errors (but all others application-specific and between 1 and 127), to the operating system.



If you want to create custom widgets, like say a speedometer like gauge, and you want to use it in the .ui file, things become a bit messier since you'd need to provide the implementation to Qt Designer also.

I don't usually do that.  Instead, I use the base class in Qt Designer, basically setting it in the layout, and then create a copy of the .ui file with the class name replaced, using
    sed -e 's| class="QClass"| class="myclass"|g' original.ui > runtime.ui
I then also use ONLY PySide2 bindings, since that makes the extension easier, via
Code: [Select]
def loadUi(uifile, custom=None, parent=None):
    from PySide2 import QtUiTools
    loader = QtUiTools.QUiLoader()
    if custom is not None:
        for cls in custom:
            loader.registerCustomWidget(cls)
    ui = loader.load(uifile, parent)
    del loader
    del QtUiTools
    return ui
where you supply a list of your custom classes as the second parameter to loadUi().

Note that the Qt .ui file syntax is based on XML, and is more or less human-readable.



So, I wouldn't say this is a lot of work.  A bit complicated, maybe, because of the many details, but I've listed the key points above.

In Linux, you can install both PySide2 and PyQt5 bindings at the same time.  I do find PySide2 a bit more complete, considering the .ui file loading mechanisms, so I guess I should prefer that.

For massive data processing, I recommend using Python built-in multiprocessing module, as the current Python interpreter can only execute code in one thread at a time per process.  Using parallel processes allows your Python code to use more than one CPU core concurrently.  Even better solution is to write it in C or some other compiled code into a dynamic library; just make the Python-facing API thread-safe, and use threads (pthreads in C).

One particular benefit of using Qt is that its graphics primitives are pretty well optimized to work on things like OpenGL ES; even moreso if you use QtWidgets.QOpenGLWidget.  See QtGui.QOpenGLFunctions for what is available (via .context().functions()).

For example, if you wanted an oscilloscope-type display, then having a list of QtCore.QPoint values defining the samples, each list will be rendered by hardware in a single call.  Similarly for blitting textures.  Remember that PNG images have transparency, and OpenGL and OpenGL ES supports textures with transparency.  A speedometer dial you could render by having three images: the background image, an image of the dial arrow shadow in center position (horizontal or vertical), and an image of the dial arrow in center position.  When rendering, the background would be copied, then the rotated shadow (with alpha channel) drawn, and finally the rotated arrow (with alpha channel) drawn.  With a bit of preparation, the three images would be stored in an OpenGL buffer, so the entire thing would basically be rendered in hardware.  Even though you're using Python.
« Last Edit: November 11, 2024, 10:52:09 am by Nominal Animal »
 
The following users thanked this post: SiliconWizard

Offline Psi

  • Super Contributor
  • ***
  • Posts: 10330
  • Country: nz
Re: GUI with gauges in python
« Reply #14 on: November 11, 2024, 10:59:02 am »
why is a callback an issue?
A callback isn't, nested callbacks, once deep enough, are.

And best not to even think about nested interrupts containing nested callbacks.  :scared:
Greek letter 'Psi' (not Pounds per Square Inch)
 

Offline djsb

  • Frequent Contributor
  • **
  • Posts: 974
  • Country: gb
Re: GUI with gauges in python
« Reply #15 on: November 11, 2024, 12:06:26 pm »
David
Hertfordshire, UK
University Electronics Technician, London, PIC16/18, CCS PCM C, Arduino UNO, NANO,ESP32, KiCad V8+, Altium Designer 21.4.1, Alibre Design Expert 28 & FreeCAD beginner. LPKF S103,S62 PCB router Operator, Electronics instructor. Credited KiCad French to English translator
 
The following users thanked this post: voltsandjolts

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15607
  • Country: fr
Re: GUI with gauges in python
« Reply #16 on: November 12, 2024, 07:35:56 pm »
Thanks to Nominal - I haven't written any GUIs with Python so far, but his example works fine (just tested) and this will be a handy start if I need to.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7126
  • Country: fi
    • My home page and email address
Re: GUI with gauges in python
« Reply #17 on: November 13, 2024, 12:41:06 am »
I'm also completely serious here.  I'd love to do a full example, say as a new thread here, but I just don't know what use case other people would find most interesting.  It's a crucial choice, because it defines how useful my efforts would be to others!  The web is already full of examples that don't really fit practical needs at all.

I could very easily make an example where you use any microcontroller with a couple of pots and LEDs, maybe a button, and an example interactive Python GUI talking to it via serial.

I have some 128×32 and 128×64 OLEDs, so another I could do is an old-style Pixel Editor (remember sprite editors of C64 era?).  Or maybe font tester?

Even a custom serial console is doable, it's just that full VT220/ANSI escape code parsing takes quite a few lines (because each command needs their own handler, and even minimal support needs at least a couple of dozen) and I'd need help verifying they are all implemented correctly.

A command/response interface would be much easier, even if you used a binary protocol.  It could even be an alternative UI for the pots-and-LEDs microcontroller case.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf