TFT GUI Problems

Here we can discuss about the problem found
User avatar
Electroguard
Posts: 277
Joined: Mon Feb 08, 2021 6:22 pm
Has thanked: 55 times
Been thanked: 129 times

TFT GUI Problems

Post by Electroguard »

Hi CiccioCB, I assume you would wish feedback about a couple of TFT GUI limitations which will probably also affect the Epaper GUI.
To be clear, the TFT GUI is an excellent feature, allowing sophisticated results fairly easily, as seen here:
ta.jpg

But usage is greatly restricted by:
  • inability for touch events to RETURN
  • inability for defining GUI handlers using variables

(both shown in pic below)
records.jpg

Instead of using a single array variable in a FOR TO loop to define multiple objects, they must all be uniquely explicitely declared individually into the script (there are still 20 more lines of unseen code just for the small 'Records' page, and many more pages), and similarly for everything that subsequently references every object.
This makes the resulting script bloated and brittle because even a slight change can break it - making it unsuitable for publishing to a hacker community.
It is actually possible to define handler objects using an array, but a gui.target array bug returns the value of its own indexed target element instead of the users indexed element. Even worse, it fails to recognise any of the defined handlers, so instead of re-using existing handlers it keeps creating new ones until using up its allocation of objects (see the diagnostic script included below).
The situation could be improved enormously if the gui.target bug could be fixed thereby allowing arrays to be used for gui handlers.


Another major restriction is that GUI touch events can only GOSUB to branches, causing memory leaks by always pushing new return addresses onto the stack without being able to RETURN to them by touch button (the wlog window shows decreasing ramfree after using the 'EXIT' button).
A fairly quick and simple solution could be if ontouch events could recognise "RETURN" as a valid reserved word in place of the branch label (to trigger a RETURN back, rather than jump forward to another GOSUB branch).


A less crucial problem is that the graphical width in pixels of gui.textline and gui.button text strings is crucial for displaying the text string correctly, but is not possible to calculate for proportional fonts, causing the widths fields to be discovered by trial and error, and only valid for each item.
The guesswork could be removed by a function which returned the appropriate width and height in pixels of a specified text string and font.


A couple of minor suggestions:
GUI.Textline is not touchable so could benefit from being assigned an event handler.
GUI.Button could benefit from having a transparent background capability, and gui.SETSTYLE ability.



But the TFT Thermostat controller above (which also now reads data from remote ble sensors) shows how amazing Annex already is even without benefit of improvement suggestions.


Diagnostic snippet for showing effects of gui.target bug:

Code: [Local Link Removed for Guests]

'Diagnostic for gui.target bug, should re-use pagefuls of object handles, but TFT shows it failing after running out
'Wlog window shows gui.target bug keeps creating new object handles until running out and erroring with -1.
pagelength=3
pages=5
gui_objects=10  'enough for more than 3 pages of objects, but only 1 pageful of re-used objects should be necessary
dim handler(gui_objects)
gui.init gui_objects, black
gui.autorefresh 50,1    
x=0: y=0: w=100: h=30: f=3
for page=1 to pages
 for line = 1 to pagelength 
  handler(line) = GUI.button(x,y+(h*line),w,h,str$(line+((page-1)*pagelength)),f,0,0,1,cyan,black,0,0
  wlog handler(line)
 next line
 pause 2000
next page
You do not have the required permissions to view the files attached to this post.
cicciocb
Site Admin
Posts: 414
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 92 times
Been thanked: 297 times
Contact:

Re: TFT GUI Problems

Post by cicciocb »

Hi Robin,
It is not very clear for me what you describe here, I need to give a look at it.

I tried your "snippet" and I can see 2 main issues in the code :
1 - you declare max 10 objects ( gui_objects=10 ) but your code creates 15 objects ( 5 pages of 3 objects) : This explains why you have -1
2 - you store the handler in a simple array, you should use a 2 dimensions array (ex. handler(page, line) )

I'll try to play a little bit and I'll come back to you
cicciocb
Site Admin
Posts: 414
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 92 times
Been thanked: 297 times
Contact:

Re: TFT GUI Problems

Post by cicciocb »

HI Robin,
I cannot find any particular issue in the GUI.
Just to clarify, I wrote this simple snippet

Code: [Local Link Removed for Guests]

'Little example explaining how do cross reference with the handler returned 
' by the GUI functions
nb_rows = 5
nb_cols = 3
gui.init 50, black' max 50 objects
' let's create some background objects to cheat the handler returned by the GUI.xxx functions
gui.circle 50, 20, 100, red
gui.circle 160, 20, 100, green
gui.box 50, 130, 100, 100, blue
gui.box 160, 130, 100, 100, yellow
' this variable will contains the handler of the next object defined, useful to determine who is it
first_handler = 0 

'create a grid of buttons
for row = 1 to nb_rows
  for col = 1 to nb_cols
    id = gui.button col * 90 - 60, row * 40 - 35, 80, 30, "But r=" + Str$(row) + " c=" + Str$(col), 1, 10
    gui.setevent id, TOUCH, mytouch
    if (row = 1) and (col = 1) then first_handler = id ' get the first handler
    wlog "id=" , id
  next col
next row

' add other buttons using "classic" variables as reference
ok = gui.button 30, 200, 100, 35, "OK"
gui.setevent ok, TOUCH, ok_pressed

cancel = gui.button 180, 200, 100, 35, "CANCEL"
gui.setevent cancel , TOUCH, cancel_pressed

gui.autorefresh 50, 1
wait

mytouch:
'computes the object taking into account that there are several rows of 3 columns
id = gui.target - first_handler
col = (id  mod 3) + 1
row = id \ 3 + 1 ' integer division
but$ = "COL=" +str$(col) + " ROW=" + str$(row)
wlog "id="; gui.target , "ram="; ramfree, but$

' now, to show how use it, I'll change the text of the button row = 3 and col = 2
row = 3 : col = 2
' computes the handler of the button
id = (row - 1) * 3 + (col - 1) + first_handler
gui.settext id,  but$ ' set the text in the central button
gui.setcolor id, green ' set the color to green
return

ok_pressed:
wlog "OK pressed"
return

cancel_pressed:
wlog "cancel pressed"
return
This program creates a grid of button like the image below
image.png
Clicking any button of the grid will write the coordinates of the button inside the central button.
All the buttons of the grid are created dynamically and use the same event handler routine.
All the references are computed and not stored in an array.
The buttons at the bottom (OK and CANCEL) use a separate event.

You are probably mixing the handler references coming from the GUI with the array that you are using to store the handlers.

To clarify, the handler returned from the GUI.xxx functions, is a simple incremental number starting from 0.
So, if you want create "dynamically" the objects, you just need to store the first handler and compute its index.
For example, if you create a list of 10 buttons, the ID of the 9th button is simple the ID of the first + 8
The example I did shows that using rows and columns to show how extract and create the index of the element.

I found no problem with the RETURN and absolutely no memory leak.
Could you, please, post a little snippet showing the issue you talk about ?
You do not have the required permissions to view the files attached to this post.
User avatar
Electroguard
Posts: 277
Joined: Mon Feb 08, 2021 6:22 pm
Has thanked: 55 times
Been thanked: 129 times

Re: TFT GUI Problems

Post by Electroguard »

In the Settings sub-menu shown in the picture, the Exit button has no way to RETURN to the previous calling page, because touch events can only Gosub forward to a specified branch name, therefore they can never return back from where they came.

So the only way for the Exit button to go to the 'home' page which called it, is to Gosub it yet again, without ever returning from the previous Gosub.

Likewise, to select any sub-menu item such as 'Defaults' will trigger another Gosub to a branch which cannot be returned from back to the sub-menu by touch button, cos all touch events are one-way Gosub's to a specified branch name.
I don't know how to explain it any clearer... but a simple solution would be if 'return' could be specified as an event branch name to make the event Return back instead of Gosub forward.

In the case of the Thermostat, with multiple large 'pages', Gosubing forward to those pages yet again, instead of returning back to them, causes the memory leakage shown earlier... which is perhaps not surprising considering that all page changes must push more and more non-returned addresses onto the stack which will never be untangled.
The earlier pic with evidence of memory leakage shows the consequences of repeatedly calling the Thermostat 'home' page due to being unable to Return to it. Without changing pages, the bloated script (well over 1000 lines) runs happily for days/weeks without any memory leakage.

Touch events being non-Returnable is not a problem for me cos I use workarounds such as automatically rebooting if mem gets low - but I was trying to raise awareness of what seemed like a problem for tft gui and epaper gui, so if it's not actually a problem then please just forget I mentioned it.

t3.jpg
You do not have the required permissions to view the files attached to this post.
cicciocb
Site Admin
Posts: 414
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 92 times
Been thanked: 297 times
Contact:

Re: TFT GUI Problems

Post by cicciocb »

Hi RObin,
It is still not clear for me but, basically, what is your need ?
To have multiple sub-pages called from a main one?
User avatar
Electroguard
Posts: 277
Joined: Mon Feb 08, 2021 6:22 pm
Has thanked: 55 times
Been thanked: 129 times

Re: TFT GUI Problems

Post by Electroguard »

Thanks Francesco, but I don't have a need... I was just trying to be helpful by pointing out a bullet which I had already dodged.
Other things on my mind at the mo cos just had an hours power failure which I'm still trying to recover from.
cicciocb
Site Admin
Posts: 414
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 92 times
Been thanked: 297 times
Contact:

Re: TFT GUI Problems

Post by cicciocb »

Hi again,
this is a little, very minimalist, demo code showing how use multiple pages and how jump between them.
It uses a simple stack mechanism to track the current page and the previous one.
There is a main page that points on several subpages and each subpage can go back to the main one or call another subpage.
Using exit from any page will come back to the previous one until reaching the main page.
I tried to comment as much as possible to give an idea on how manage the pages.

Code: [Local Link Removed for Guests]

'Little example explaining how do multi pages interface
' using the GUI functions

page_active = 0 ' this is the current shown page pointer
dim pages_stack(20) ' this is the page stack (max 20 pages stacked)
pages_stack(0) = 0 ' the first page is the main (0)
pages_stack_pointer = 0  ' the current position in the stack
gui.autorefresh 50, 1  'autorefresh
gosub page_selector 'shows the active page
wait

'this sub simply select the active page converting from number to page labels
'each new page must be included here
page_selector:
select case page_active
case 0: gosub main_page
case 1: gosub sub_page1
case 2: gosub sub_page2
case 3: gosub sub_page3
case 4: gosub sub_page4
end select
return

'this is a list of "wrapper" enabling to jump directly to any page
'for each page, a "wrapper" must be included
go_page1:
go_subpage 1
return

go_page2:
go_subpage 2
return

go_page3:
go_subpage 3
return

go_page4:
go_subpage 4
return

'this is the gosub called at each "exit" from the any page
' goes back to the previous called page
go_return:
if (pages_stack_pointer > 0) pages_stack_pointer = pages_stack_pointer - 1
page_active = pages_stack(pages_stack_pointer)
gosub page_selector
return

'this is called by each wrapper. stores the current page in the stack 
'and jumps to the page given as argument p
sub go_subpage(p)
pages_stack(pages_stack_pointer) = page_active 
pages_stack_pointer = pages_stack_pointer + 1
page_active = p
gosub page_selector
end sub

'this is the main page
'can contains any object and buttons to jump toward the other pages
'it should not contains any "exit" button
main_page:
gui.init 50, black' max 50 objects
gui.textline 100, 0, 100, 20, "Main Page", 2
gui.circle 50, 20, 100, red
gui.circle 160, 20, 100, green
gui.box 50, 130, 100, 100, blue
gui.box 160, 130, 100, 100, yellow
ret = gui.button 30, 30, 100, 40, "goto page 1"
gui.setevent ret, TOUCH, go_page1
ret = gui.button 150, 30, 100, 40, "goto page 2"
gui.setevent ret, TOUCH, go_page2
ret = gui.button 30, 120, 100, 40, "goto page 3"
gui.setevent ret, TOUCH, go_page3
ret = gui.button 150, 120, 100, 40, "goto page 4"
gui.setevent ret, TOUCH, go_page4
return

'this is a subpage
'can contains any object and buttons to jump toward the other pages
'it should contains an "exit" button
sub_page1:
gui.init 50, black' max 50 objects
gui.textline 100, 0, 100, 20, "Sub page 1", 2
gui.circle 50, 20, 100, blue
gui.circle 160, 20, 100, blue
gui.box 50, 130, 100, 100, blue
gui.box 160, 130, 100, 100, blue
ret = gui.button 150, 120, 100, 40, "goto page 3"
gui.setevent ret, TOUCH, go_page3
ret = gui.button 50, 180, 100, 40, "Exit"
gui.setevent ret, TOUCH, go_return
return

'other subpage
sub_page2:
gui.init 50, black' max 50 objects
gui.textline 100, 0, 100, 20, "Sub page 2", 2
gui.circle 50, 20, 100, green
gui.circle 160, 20, 100, green
gui.box 50, 130, 100, 100, green
gui.box 160, 130, 100, 100, green
ret = gui.button 150, 120, 100, 40, "goto page 4"
gui.setevent ret, TOUCH, go_page4
ret = gui.button 50, 180, 100, 40, "Exit"
gui.setevent ret, TOUCH, go_return
return

'other subpage
sub_page3:
gui.init 50, black' max 50 objects
gui.textline 100, 0, 100, 20, "Sub page 3", 2
gui.circle 50, 20, 100, violet
gui.circle 160, 20, 100, violet
gui.box 50, 130, 100, 100, violet
gui.box 160, 130, 100, 100, violet
ret = gui.button 150, 120, 100, 40, "goto page 2"
gui.setevent ret, TOUCH, go_page2
ret = gui.button 50, 180, 100, 40, "Exit"
gui.setevent ret, TOUCH, go_return
return

'other subpage
sub_page4:
gui.init 50, black' max 50 objects
gui.textline 100, 0, 100, 20, "Sub page 4", 2
gui.circle 50, 20, 100, red
gui.circle 160, 20, 100, red
gui.box 50, 130, 100, 100, red
gui.box 160, 130, 100, 100, red
ret = gui.button 150, 120, 100, 40, "goto page 1"
gui.setevent ret, TOUCH, go_page1
ret = gui.button 50, 180, 100, 40, "Exit"
gui.setevent ret, TOUCH, go_return
return
User avatar
Electroguard
Posts: 277
Joined: Mon Feb 08, 2021 6:22 pm
Has thanked: 55 times
Been thanked: 129 times

Re: TFT GUI Problems

Post by Electroguard »

Thanks for that Francesco, but it adds much more complexity than just being able to RETURN using a simple touch button event.

fleximenusmall.jpg

A few months ago I did a re-usable general purpose TFT GUI FlexiMenu, not really for my own use, but more as a learning excercise for TFT GUI objects, with the intention of publishing a useful TFT Menu for others that could automatically adapt itself to their screen size and orientaion and font size etc. All buttons re-position automatically... so the same script displays correctly on eg 2.8" landscape or 4" portrait.

It allows stepping up and down a multi-page list, and selecting the highlighted item, using either touch buttons or M5 Stack hardware buttons.
And of course any item visible on any page can be selected directly by touching it.
Additionally it offers Page Up and Page Down touch buttons for faster navigation through multiple pages of long lists.
When scrolling individually through a page of items it changes pages if available, or wraps from top to bottom and vice versa, whereas when using Page Up or Page Down buttons it stops at the top or bottom page.

If the TFT GUI was capable of re-using an array of just a pageful of handler objects for potentially many pages of items it would have been easy, but it turned into a page tracking nightmare. So trying to include your suggestion for page navigation would not have been possible for me.
I did actually achieve everything though, but the cumbersome workarounds of various limitations made the result too bloated and fragile to publish.
You do not have the required permissions to view the files attached to this post.
User avatar
Electroguard
Posts: 277
Joined: Mon Feb 08, 2021 6:22 pm
Has thanked: 55 times
Been thanked: 129 times

Re: TFT GUI Problems

Post by Electroguard »

'this is the gosub called at each "exit" from the any page
' goes back to the previous called page
go_return:
if (pages_stack_pointer > 0) pages_stack_pointer = pages_stack_pointer - 1
page_active = pages_stack(pages_stack_pointer)
GOSUB page_selector
return
Francesco, your code for RETURNing to previous branches via touch events actually goes forward to them using another GOSUB.
So you are actually doing exactly the same 'workaround' as I'm doing in the Thermostat... forced to GOSUB forward to previous branches cos is not possible to RETURN back to them.
Therefore the system stack (not your own page management) must keep accumulating unused return addresses which never get untangled and will eventually choke it.

That's the point I've been trying to make, cos it will apply to the epaper gui also.
It could be easily resolved IF it might be possible to parse the touch event branch name looking for "return" as a reserved word (which cannot be a valid branch name anyway) to eg: issue a RETURN instruction instead of a GOSUB return.

Or perhaps a simpler and better solution could be to add that facility to the standard GOSUB instruction, giving it the generic capability to GOSUB RETURN back to where it came from as an alternative equivalent to RETURN (presumably the touch event gosubs invoke the inbuilt gosub instruction).
cicciocb
Site Admin
Posts: 414
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 92 times
Been thanked: 297 times
Contact:

Re: TFT GUI Problems

Post by cicciocb »

[Local Link Removed for Guests] wrote: [Local Link Removed for Guests]Mon Nov 29, 2021 12:32 pm
'this is the gosub called at each "exit" from the any page
' goes back to the previous called page
go_return:
if (pages_stack_pointer > 0) pages_stack_pointer = pages_stack_pointer - 1
page_active = pages_stack(pages_stack_pointer)
GOSUB page_selector
return
Francesco, your code for RETURNing to previous branches via touch events actually goes forward to them using another GOSUB.
So you are actually doing exactly the same 'workaround' as I'm doing in the Thermostat... forced to GOSUB forward to previous branches cos is not possible to RETURN back to them.
Therefore the system stack (not your own page management) must keep accumulating unused return addresses which never get untangled and will eventually choke it.

That's the point I've been trying to make, cos it will apply to the epaper gui also.
It could be easily resolved IF it might be possible to parse the touch event branch name looking for "return" as a reserved word (which cannot be a valid branch name anyway) to eg: issue a RETURN instruction instead of a GOSUB return.

Or perhaps a simpler and better solution could be to add that facility to the standard GOSUB instruction, giving it the generic capability to GOSUB RETURN back to where it came from as an alternative equivalent to RETURN (presumably the touch event gosubs invoke the inbuilt gosub instruction).
Hey Robin,
have you tried the code and see if there are memory leaks?

You are probably misinterpreting, the GOSUB page_selector simply redraw the page and comes back, do not remains stuck as you describe in your code.

What I proposed is a central function that uses a stack pointer to select what is the active page to be shown and come back to the previous one.
No gosub between pages, to pass from a page to another one there is just a variable to change, page_active

If I understood well, the way your program work, it should have the same issue in any other programming language.

In fact, you must take into account that the touch events are asynchronous (can happen at any time) while the interpreter is still running other code in background.
The way the interpreter works, when an async event is triggered (for example the touch), the running code is interrupted with a virtual GOSUB and then, when the "interrupt handler" routine terminates with a RETURN, the code can continue normally.

Issuing a "RETURN" when clicking on the "EXIT" button will have an unknown effect as this RETURN can happen when the code is inside another routine generating an uncontrollable effect.

Just for fun, I will implement this functionality and you'll see the result, maybe it could work for you.
Post Reply