Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request: Support for sub-pages / layouts #71

Closed
Grtschnk opened this issue Oct 25, 2018 · 16 comments
Closed

Request: Support for sub-pages / layouts #71

Grtschnk opened this issue Oct 25, 2018 · 16 comments
Assignees
Milestone

Comments

@Grtschnk
Copy link

Thank you for your great software! Compiling and running it on ESP32 with ESP-IDF. Enjoying it a lot. :)
Issue below:


Problem:
I am presenting data on different pages (e.g. Log, Settings, etc..). I also want to display some status information all the time (battery status, wifi connectivity, etc.), regardless of which page is selected. At the current moment, I would need to create a separate 'Status page' and switch to it periodically (or on request), or I would need to create an element multiple times for each page, and update all of them,

Idea:

  1. Option: Elements can be added to multiple pages at once
  2. Option: Multiple pages can be displayed at once: display the "banner" page on top of another page (either in horizontal/vertical direction or in layer direction).

With both options, the library user needs to be mindful of leaving space for the "Status" related elements (either leaving space inside the page for these elements, or leaving space as the area will be overlapped by a status page). But I think this is not an issue, as it does not differ much from the current situation. After all, we are doing embedded programming and not some HTML ;)

I could imagine possible necessary code changes would relate to issues #9 and #24

I am currently looking at the code, see if I can add this functionality to my fork. Is this functionality that could be added rather easiliy (by myself) or do you think this would require more rework / is not possible?

(I also added drivers/support for loboris TFT driver for ESP32, let me know if you'd like a pull request - currently a bit concious of my coding and git abilities)

@ImpulseAdventure
Copy link
Owner

Great suggestion.... some people have previously asked for the same concept: marking some elements as global, so that they are active/visible even after transitioning between pages.

Giving some more thought to it, I think there are several ways that this could be done, but my preference would be to implement an approach such that:

  • Users only need to create an element and mark it as belonging to a "global" page
  • Internally, no duplication of storage elements would occur. This way, one could add a large number of pages and not consume excess memory for any global element. Replicating elements would have also made it difficult to update & synchronize the elements later, so I'm less keen on a duplication strategy.
  • The way I intend to implement it is to modify a couple of the page handlers (for redraw and touch handling) to process both the current page and (if enabled) the global page. Naturally, users would need to be mindful of any visual overlaps (active page takes precedence) as you point out.

Good news -- I have a prototype working of this now! If all goes well with the testing, I might be able to roll this feature out soon for you to try.

As for the loboris TFT driver, that is great to hear. Assuming the changes are relatively self-contained (ie. basically a new GUIslice_drv_loboris file), I'd be happy to take a look at a PR, though note that I have a couple other pending PRs that I would likely need to address first. Perhaps you can open a separate issue to describe the loboris TFT support.

thanks!

@espHorst
Copy link
Contributor

Would it be possible to display any two pages at the same time? This would even enable swtiching in between different status pages.

@ImpulseAdventure
Copy link
Owner

ImpulseAdventure commented Oct 27, 2018

@espHorst -- interesting idea.

I have revised my prototype to support selecting [and dynamically changing] the "global page".
The new example I created has a status bar on top and a tab dialog with content that changes according to the "current page".

It is very easy for the user to do this. Basically you create pages as before, but one (or more) page can contain the "global elements". Let's call this the global page.

  • Call SetPageCur() to switch between the active (primary) pages
  • Call SetPageGlobal() (a new function) to select the global page that will also be visible. Calling with GSLC_PAGE_NONE will disable the global page mode.

Is "global page" a good name for this feature/mode? I am open to changing it if you can suggest a more intuitive name.

@espHorst
Copy link
Contributor

espHorst commented Oct 27, 2018

Just from a more or less philosophic point of view: what distinguishs a global from a normal page? Would it make sense ( or is it to complicated ) to allow for an arbitrary number ( in an array of pointers ) of pages zo be displayed at the time. Setpagecur just sets array[0] and there could be a function setpage(index, pPage) for setting any of the displayed pages.

@ImpulseAdventure
Copy link
Owner

Conceptually there would be little difference in the treatment of a “global” page versus the “current” page. I was mainly attempting to simplify the usage model somewhat, and changes to the API. That said, I see value in extending this concept.

Multi-layer Concept

Thinking further about the implementation, I see that the current solution could also go a long way to address the request to support pop-up dialogs in #9 (which could offer confirmations or even a reusable pop-up numerical entry page, for example). I suspect the most common use-cases might lend themselves to using 3 “layers” (where a layer is really a reference to a page): back, current and top, though of course it could be generalized further if it made sense. Do you think three might be enough?

In this way, a “back” layer could provide the status and “chrome” that could be common between pages. In my gslc_ex19 example, I have put a tab dialog and the associated tab selector buttons on the “back” layer. The “current” layer is the one that users will most commonly interact with (this is named the “current page” today). A “top” layer could be activated occasionally to allow for alert dialogs (yes/no) or data entry. Naturally, these layers could be assigned to GSLC_PAGE_NONE to disable.

Thanks to your suggestion of enabling dynamic changes to these “layers”, we could very easily select a new “top” layer to provide any arbitrary input dialog / modal to the user while the “current” visible layer stays as-is. For example, select PG_ALERT_YES_NO or PG_ENTRY_NUM as needed for the top layer.

The nice thing about this layered approach is that we can show through the other layers, so a data entry or alert box would still reveal the elements dynamically updating in the back and current layers.

Furthermore, it would be a simple matter to add a flag per-layer that enables or disables the touch. Therefore, when we present a modal dialog in the top layer, the back and current layers have their touch disabled. Deactivating this top layer would restore the touch on prior layers.

I was thinking of calling these additional two pages "layers", but really they are just two extra "pages" that can be shown at the same time as the current "page". Introducing the concept of a "layer" may help clarify the mechanics / restrictions / usage model.

Would it make more sense to call these layers?

API

There are a couple things we should keep in mind:

  • Draw order: we would want to render back, current and top in that order
  • Touch: we would want to accept touch input in reverse order (top, current then back).
  • The “current page”: Today, users are likely to leverage the APIs involving the “PageCur” (ie. To determine which page we’re on, or what page to transition to). If one provides an arbitrary list of pages/layers, then one would have to decide how to preserve this functionality.

If we restrict it to 3 layers, then the APIs could be tailored to the common use-cases, such as:

  • SetLayerBack(nPageId)
  • SetPageCur(nPageId) - existing
  • SetLayerTop(nPageId)
  • SetLayerTouchEn(nPageId,bTouchEn)

If we make the set of layers arbitrary then we could offer:

  • SetPageCur(nPageId) - existing
  • SetLayerPage(nLayerInd,nPageId)
  • SetLayerTouchEn(nLayerInd,bTouchEn)

To present a modal popup dialog, we might:

  • Save layer touch enables
  • SetLayerTop(PG_ALERT_YES_NO)
  • SetLayerTouchEn(PG_BACK,false)
  • SetLayerTouchEn(PG_MAIN,false)

Perhaps one could even offer a simple wrapper for users that automatically saves/restores the state, so the user just calls:

  • ShowModal(PG_ALERT_YES_NO)
    Saves state, assigns top layer and disables lower layer touch per above
  • ... handle popup dialog
  • HideModal()
    This sets SetLayerTop(GSLC_PAGE_NONE) and restores state

With all of the above, we could substitute "Page" for "Layer" if it makes more sense.

Implementation

Behind the scenes, we could certainly implement this with an array of pages (either pointers or page IDs).

One important decision point is whether to include the storage of these layers in the existing GUI structure (wherein I think we would want to limit it to 3 layers), or if we provide a new set of APIs for the user to register their own array of layers/pages.

When introducing features like this I have to be mindful of the memory footprint (particularly on SRAM) for Arduino devices. If is incorporated internally, then 3 layers would hopefully consume only ~ 8 bytes for 3 Page IDs and the current/saved per-layer touch enables.

Thoughts?

@espHorst
Copy link
Contributor

espHorst commented Oct 27, 2018

Here some - maybe silly as I don't have a very deep understanding of some parts of GUIslice - thoughts:

A) modal dialog
I'm not sure if there will be a problem with updates of the lower layers realising the top page concept. Every time when a lower layer is updated it draws directly in the display (at least I assume this) and thus might overwrite the modal dialog. Even if the modal dialog repaints every time I think there will be some flicker as the display access is not buffered for all displays.
In order to get some basic requirements for further brainstorming about a suitable modal dialog architecture:

  • Do we need stacked modal dialogues?
  • Does the background really need to be updated during the display of the modal dialog?

If we don't need either then just exchanging the current selected page(s) with the modal dialog might work. As long as the display is not cleared you will see the old display content in the background of the modal dialog.

B) back and cur page
I hesitate a little bit to restrict the display to only two pages at the same time.
Would it be possible to add a page as an element?
If yes, then this might give the chance to use only memory for a "back" page if it is required.
And it offers the ability to combine as many pages at the same time as you like.
If you need a status bar (or even many of them, or even if you would like to combine multiple pages):

  • add "root" page
  • add two (or more) page elements to the root page - like you would add button elements
  • element 1: the "status" page ( gslc_tsPage m1_asPage[MAX_PAGE] )
  • element 2: the "cur" page ( gslc_tsPage m2_asPage[MAX_PAGE] )
  • element 3: ...

On each page element you can call "setCurPage" to select which of the individual pages from mX_asPage should be displayed.
This would even make the design of the pages easier: The page elements - and thus the pages that are displayed by the page elements - will then have only the size that is defined for the element, thus the different pages will not interfere with respect to the coordinates.

This concept might even be easier for the user. At the beginning - and maybe for 80% of the users all the time - you don't need to bother with the bottom, cur and top page construct. If you need it, then it is just an element.
But - as already mentioned - this might be a silly idea.

@ImpulseAdventure
Copy link
Owner

ImpulseAdventure commented Oct 27, 2018

Thanks for the feedback!

In general I would prefer to keep the implementation simple yet scalable, so I agree with your thoughts on the modal dialog requirements. I don't think modal stacking is likely to be common, particularly with smaller TFT displays, and the benefit of background updates is marginal.

Modal Dialog

Your suggestion of leaving the main page unrefreshed and drawing the overlay (eg. modal dialog) seems reasonable but we'd have to disable the redraw of background upon needs-full-redraw (bRedrawFullPage) detection when updating the modal dialog. Usually the bRedrawFullPage is triggered when updating an element (with no fill, ie. transparent).

Page within an Element

Embedding a page within an element is definitely worth exploring further!

I'm not sure if you are aware, but I did implement the notion of a "compound element" a long time back (see the XSelNum example). The compound element could contain other elements, which enabled one to create reusable widgets (eg. numeric keypad, etc.) However, I like the idea of leveraging the tsPage structure more than the "compound element" implementation (my preference would be to deprecate this as it added complexity) with the page method being more flexible.

So, to avoid any confusion in terminology, let's assume we can create a new tsXLayer element with ElemXLayerCreate().

Example Application

For the purposes of our discussion, perhaps we can take following example (based loosely on what you provided) in order to ensure I understand your proposal: 2 real pages, a common status bar and a popup dialog.

  • Main page (E_PG_MAIN)
    • E_ELEM_LAYER_STAT1: Status bar layer (point to E_PG_STATUS)
    • Misc elements
    • (Possibility of triggering the E_ALERT_YES_NO popup)
  • Config page (E_PG_CONFIG)
    • E_ELEM_LAYER_STAT2: Status bar layer (point to E_PG_STATUS)
    • Misc elements
    • (Possibility of triggering the E_ALERT_YES_NO popup)
  • Alert popup page (E_ALERT_YES_NO)
    • Text element
    • Yes / No buttons
  • Status layer (E_PG_STATUS)
    • Text element (eg. time)
    • Progress bar for battery level

With the above, I thought we might have:

  • SetCurPage() selects between E_PG_MAIN, E_PG_CONFIG. Certain controls on E_PG_MAIN or E_PG_CONFIG may trigger a call to SetCurPage(E_ALERT_YES_NO) and then a reversion back to SetCurPage(E_PG_MAIN/E_PG_CONFIG).
  • As E_PG_STATUS is really just a background page/layer, it doesn't ever become a target for SetCurPage().
  • Per your example, in order to change the background status layer (perhaps to E_PG_STATUS2), we could all ElemXLayerSetPage(E_PG_STATUS2) on whatever pages desire the new status view (eg. both E_ELEM_LAYER_STAT1 and E_ELEM_LAYER_STAT2).

Page Storage

Today, users provide explicit storage for the tsPage array and register it within the Init() call.
Arrays of elements (for a given page) are also provided by the user and registered in the PageAdd() call, associated with a given Page ID.

I was initially thinking that the Layer element might just contain a reference/index to a single page in the global page array. This way the Layer wouldn't need to incorporate page storage within its structure (I'm less keen on having page structures stored in more than one place). As a result, the call to ElemXCreateLayer() might just include the nPageId of a page that is already defined in the global page array (eg. E_PG_STATUS).

Redraw order would be managed by the user in the order in which elements were added to the root page.

From your example:

add "root" page
add two (or more) page elements to the root page - like you would add button elements
element 1: the "status" page ( gslc_tsPage m1_asPage[MAX_PAGE] )
element 2: the "cur" page ( gslc_tsPage m2_asPage[MAX_PAGE] )

... it seems like we would be storing multiple pages within each "Layer" element. Would you still have multiple pages also stored in the global page array (registered with Init())? I apologize if I misunderstood your proposal.

In your opinion, do you think it might be too restrictive if we made a Layer element just point to one page (that can be dynamically selected) stored in the global page array (registered by PageAdd())? Ie. What benefits do you envision we gain if we include arrays of pages within each [layer] element?

Other notes

  • Changing background status elements would be as simple as updating elements on the status (background) page.
  • Events (eg. touch, redraw, tick, etc.) can probably be distributed in the same event tree propagation that already exists

The page elements - and thus the pages that are displayed by the page elements - will then have only the size that is defined for the element, thus the different pages will not interfere with respect to the coordinates.

From the above, are you are suggesting that we update the clipping region to the element's bounds in order to ensure no overlap? Or that a relative coordinate system is provided to the layer sub-elements?

If it sounds like the proposal is closer to what you are envisioning, I'll start to dig through the code to see if there are any gotchas, but it sounds reasonable so far.

Thanks again for the great feedback -- I really appreciate the input!

@espHorst
Copy link
Contributor

espHorst commented Oct 28, 2018

I'm really impressed about the structured way you approach the new feature. I see that you definitely picked up all the ideas and came up with even more and even better ones.
Here some further thoughts/proposals on the various topics:

Example Application

Perfect!

Page / Layer / Layout

Layer is definitely a better word than page. On other gui systems I have seen often the name
Layout for this element, as it "layouts" other elements. Like

  • a window (GUIslice: tsGui) contains a layout (GUIslice: tsPage)
  • the layout (GUIslice: tsPage) contains elements and there exist elements which are further layouts (your proposal: layer)
    Maybe Layout would be a good name describing what the element does.

Page/Layer/Layout: One name for multiple things

At the moment I'm struggling sometimes with the naming page / your proposed name layer / my proposed name layout. They seem to be one and the same name for multiple things.
page/layer/layout seem to refer to

  • one page, the "page itself"
  • a set of pages (the collection of pages in the array)
  • the management functions for a page set

I propose the following naming:

  • Stack or Tab or more to come (display of one layout and management of the layoutset, see below)
  • LayoutSet (just the array pointing to layouts)
  • Layout (one layout itself)

I propose the following over all structure:

  • tsGui
  • tsPageArray (a set of pages) including multiple
  • tsPage (on page, the page itself) including multiple
  • tsElement

up to here the same as today, now there are new elements coming in.
Instead of a compound element there is a stack (and in the future maybe a tab, ...) element

  • tsStack, this is the element that is added, it manages a layoutSet, this is one or multiple
  • tsLayout, this includes a "elementSet", one or multiple
  • tsElement

I think, this is no change in functionality to what you described, I was just thinking about another naming make the architecture more obvious.

Page Storage

The tsPage[] array(today) and in the future maybe also the tsLayoutSet[] keeps the pointers to layouts (i. e. pages). Layouts(i.e. pages) themselves are stored as today.
Elements like tsStack or tsTab just mangage (display one of the layouts and select which one is displayed) the LayoutSet.
There may be more than one LayoutSet, typically each tsStack shows to an own LayoutSet, but all Layout Sets are all defined together at the beginning of the code, maybe even in FLASH.
I think given the possibility to have multiple LayoutSets gives the chance to structure the definition of the gui without requiring more program or data memory.

Coordinate system

Or that a relative coordinate system is provided to the layer sub-elements?

Yes, I think this is a good solution. Like on the Buttons, they already report/work with relative coordinates, don't they?

Summary

There are no incompatible changes to GUIslice, just one (in the future maybe more) element
tsStack is added. tsStack points to a layoutSet (the same as a page set, i.e. the page array today) and is responsible of displaying the selected layout. The layout displayed is selected by calling the according a tsStack function tsStack.showLayout(index in the layoutSet).

What do you think?

@espHorst
Copy link
Contributor

espHorst commented Oct 28, 2018

Here an example how the layoutSets can be used without needing any data memory:

//this allows us to use the new naming just by reusing the glsc_tsPage
#define glsc_tsLayout gslc_tsPage

glsc_tsLayout layoutMain;
glsc_tsLayout layoutConfig;
glsc_tsLayout layoutStatus;

//create a LayoutSets, using const moves the pointer array to flash (at least for the STM32F1)
glsc_tsLayout* const layoutSetUser[] = {&layoutMain,&layoutConfig};
glsc_tsLayout* const layoutSetStatus[] = {&layoutStatus};

I have successfully compiled this on my STM32F1.
At first I made an mistake placing the const keyword. Thus as information:

//NOTE: remember that const applies to what is on its immediate left, if nothing on the left then on the right. 
//http://duramecho.com/ComputerInformation/WhyHowCppConst.html

I think that even the layouts themself can be completely defined in flash just
by initializing the struct immediately after definition. Nowadays ANSI C seems to support

In (ANSI) C99, you can use a designated initializer to initialize a structure:
MY_TYPE a = { .flag = true, .value = 123, .stuff = 0.456 };

from https://stackoverflow.com/questions/330793/how-to-initialize-a-struct-in-accordance-with-c-programming-language-standards

and

5.22 Compound Literals
ISO C99 supports compound literals. A compound literal looks like a cast containing an initializer. Its value is an object of the type specified in the cast, containing the elements specified in the initializer; it is an lvalue. As an extension, GCC supports compound literals in C89 mode and in C++.
Usually, the specified type is a structure. Assume that struct foo and structure are declared as shown:
struct foo {int a; char b[2];} structure;
Here is an example of constructing a struct foo with a compound literal:
structure = ((struct foo) {x + y, 'a', 0});

from https://gcc.gnu.org/onlinedocs/gcc-4.3.2/gcc/Compound-Literals.html
I have never tried this yet.

@espHorst
Copy link
Contributor

espHorst commented Nov 4, 2018

I have tried the struct initialization, here just a small example:

struct element_t {
    uint16_t width;
    uint16_t heigth;
};

element_t eButtonRotate = { .width = 20, .heigth = 20 };

and it works. I'm just not sure if it works in plain C (I have here the Arduino C++), literature says it does. Maybe this would be an interesting way to create the layouts.
Even without the need to call any functions in the setup() function.

Therefore this could also be a basis for #79 .
GUI builders create a "meta" language, this could be such a struct initialization header file.
But for the beginning this file could be done by hand.

Would you be interested about discussions towards this direction?
Opening a side branch in order to try and to discuss some ideas?

@ImpulseAdventure ImpulseAdventure changed the title Support for static gslc_Pages / "banner pages" Support for sub-pages / layouts Nov 5, 2018
@ImpulseAdventure
Copy link
Owner

ImpulseAdventure commented Nov 5, 2018

Thanks a lot for the great suggestions, @espHorst.

From a terminology perspective, I think another term I've seen used to describe your Layout might be a pane. Let's stick with layout for now. Note that the your ElementSets are represented today by a tsCollect (ie. an element collection).

Example

Rereading your comments, I believe I understand better what you are proposing with respect to the layoutSets. Taking the scenario I provided earlier, would this concrete example be representative of what you were suggesting?

tsGui
- CurPage (root): ptr to tsLayout[0]"User"
- tsLayout[0] "User"
  - Contains single tsCollect:
  - tsElem[0]: GSLC_TYPE_STACK
    - Manages tsLayoutSet
    - tsLayoutSet*
      - [0]: ptr to tsLayout[1]"Main" or tsLayout[2]"Config"
      - [1]: ptr to tsLayout[3]"Status"
  - tsElem[1]: GSLC_TYPE_TXT (title)
- tsLayout[1] "Main"
  - Contains single tsCollect:
  - tsElem[0]: GSLC_TYPE_BTN (text button)
  - tsElem[1]: GSCL_TYPE_TXT
  - ...
- tsLayout[2] "Config"
  - Contains single tsCollect:
  - tsElem[0]: GSLC_TYPE_TXT
  - tsElem[1]: GSLC_TYPEX_CHECKBOX
  - tsElem[2]: GSLC_TYPEX_CHECKBOX
  - tsElem[3]: GSLC_TYPE_BTN (text button)
- tsLayout[3] "Status"
  - Contains single tsCollect:
  - tsElem[0]: GSLC_TYPE_TXT (time display)
  - tsElem[0]: GSLC_TYPEX_GAUGE (progress bar)
- tsLayout[4] "Alert"
  - Contains single tsCollect:
  - tsElem[0]: GSLC_TYPE_BOX (background box, partial screen coverage)
  - tsElem[1]: GSLC_TYPE_TXT (alert message)
  - tsElem[2]: GSLC_TYPE_BTN (text button: OK)
  - tsElem[3]: GSLC_TYPE_BTN (text button: cancel)

To keep things simple I would probably start with all Layouts using absolute coordinates, rather than inheriting positioning from the parent container (Stacks / Tabs), but naturally I'll be looking for opportunities to keep this relative if possible.

From a rendering perspective, we would begin at the GUI root page/layout. We issue a PageEvent(DRAW) on Layout[0] which steps through each element in Layout[0]'s tsCollect. Upon encountering Layout[0].Elem[0], we would issue a further PageEvent(DRAW) on Layout[1].

In this particular "stack" example: When the user wishes to switch between the "Main" and "Config" screens, they wouldn't be using SetCurPage() (which would effectively change our "root" layout). Instead, they would be calling something like Layout[0].Elem[0].SetCurLayout(index=0,layout=1/2).

LayoutSet storage

Your suggestion regarding the tsLayoutSet variable length array storage without extra memory allocation is definitely worth looking at. Presumably we would still require mutability in the pointer values to enable SetCurLayout(), so not entirely clear how this should best be implemented.

Popup Dialogs

It seems like there could be a couple potential ways to support popup dialogs. The easiest would be to use SetCurPage(4) which would change the root window, and then resume with SetCurPage(0).

Alternately. assuming the LayoutSet array of ptrs to Layouts is mutable, it seems like they could also be supported by reserving one array entry (at end) as NULL. When the user wishes to display a popup dialog, Layout[0].Elem[0].SetCurLayout(index=2/reserved,layout=4) could be called. While this could potentially offer visibility of the prior page, it has a couple downsides:

  • As discussed earlier, not having the luxury of memory for a buffered display means multi-redraw flicker, unless the page is only partly redrawn (not supported in all driver modes)
  • Need to disable the underlying pages (may require an additional per-layout enable bit) so that presses on other layouts aren't accepted

For the moment I'll proceed with the more straightforward SetCurPage() method.

Meta-language

I am always open to suggestions for improving ease of use… using struct initialization could be another avenue worth exploring further (perhaps you could open up a separate issue for this?). Note that I had explored something similar when creating the ElemCreate*_P() macros to support FLASH-based UI elements in #6.

Just a heads-up: I intend to release my initial implementation of the Keyboard / GPIO control (#66) and then can begin a more thorough review of the approach you have outlined here.

@espHorst
Copy link
Contributor

espHorst commented Nov 6, 2018

thanks for the well structured example. Mainly based on this example here some findings:

  • maybe a layout is just a tsCollect - each layout only contains only a single tsCollect

  • LayoutSet storage

    Your suggestion regarding the tsLayoutSet variable length array storage without extra memory allocation is definitely worth looking at. Presumably we would still require mutability in the pointer values to enable SetCurLayout(), so not entirely clear how this should best be implemented.

    I think there is only the need to store one single pointer in the tsGui(for the "root" layout") and one in each tsStack. Those are pointing to the currently displayed layout. Do you think it is better to store the pointer itself or if it is even better to store just the index into a layoutSet? I see no need to keep the layoutSets themself mutable, do you?

  • Absoute coordinates: I agree, lets start with absolute coordinates.

  • Meta-language: I will definitely have a look on the ElemCreate*_P() macros - this sounds interesting!

    Just a heads-up: I intend to release my initial implementation of the Keyboard / GPIO control (Support Keyboard / Switch instead of Touch #66) and then can begin a more thorough review of the approach you have outlined here.

    That's not a problem. I'm not in a hurry :-)

@espHorst
Copy link
Contributor

espHorst commented Nov 9, 2018

I had a look on the ElemCreate*_P() macros. I think this is a very clever way to save ram. Are there any drawbacks in using those macros? Why don't always use macros like this and save ram? On non Arduino ATMEGA chips we could just drop the PROGMEM or just define it as empty.

@ImpulseAdventure
Copy link
Owner

@nanokatz deserves the credit for coming up with the original FLASH-based macro concept.

One of the interesting challenges in trying to move elements to FLASH was the desire to still support mutable states (such as glowing status, text content, etc.). This involved keeping some mutable flags in the RAM-based tsElemRef structures that are associated with each FLASH-based tsElem structure.

While the SRAM footprint is greatly reduced, the main hesitation in using these macros everywhere is that the user code needs to provide the majority of an element's characteristics in the initial ElemCreate*_P() call, rather than calling the API mutator functions to override specific fields of interest from the defaults. Similarly, not all element characteristics (such as button color, etc.) are currently "editable" via the APIs.

Nonetheless, the introduction of the ElemCreate*_P() macros enabled a significant boost to the SRAM efficiency & GUI capabilities for low-memory Arduino devices. Furthermore, the GUIslice Builder (#79) can also generate this arduino_min (FLASH-based) code, so it greatly improves upon the ease of utilizing these macros.

ImpulseAdventure added a commit that referenced this issue Dec 12, 2018
- This method utilizes the concept of a "Global Page"
- Example ex24_ard_tabs has been added
- Add API: SetPageGlobal()
@ImpulseAdventure
Copy link
Owner

Just a quick heads-up that I have now posted the code that I developed for the original "global page" layer strategy referenced in my Oct 27 comment. If interested, please see branch WIP71-GlobalPage and the accompanying example ex24_tabs. Code has been posted for both Arduino and LINUX/RPI. This example demonstrates a simple dialog with tabs navigating multiple "real" pages, while the tab buttons, frame and title bar are from a "global" page.

Note that I still intend to explore the more comprehensive layout strategy that espHorst referenced above, but in the meantime I thought I'd post this since there were requests to see the original proof-of-concept.

Here is a screenshot from one page of the demo:
image

Obviously the appearance of the tab dialog can be greatly improved -- this was just a proof-of-concept.

@ImpulseAdventure ImpulseAdventure changed the title Support for sub-pages / layouts Request: Support for sub-pages / layouts Feb 12, 2019
@ImpulseAdventure ImpulseAdventure self-assigned this Feb 24, 2019
@ImpulseAdventure ImpulseAdventure added this to the 0.12.0 milestone Feb 24, 2019
ImpulseAdventure added a commit that referenced this issue Feb 26, 2019
- Implement basic page stack functionality from #71 and #81 
- Add example ex24 to show simple tabbed dialog box, base layer and popup
- Add example ex25 to show popup
- Add `SetPageBase()`, `SetPageOverlay()` to complement `SetPageCur()`
- Add `PopupShow()` and `PopupHide()`
- Add `SetStackPage()` and `SetStackState()` to permit advanced controls over the page stack
- Further redraw optimizations will come later
@ImpulseAdventure
Copy link
Owner

ImpulseAdventure commented Feb 27, 2019

The basic "Page Stack" implementation has now been integrated into the core library. The code supports an arbitrary number of "pages" in a stack that can be shown concurrently on the "screen" (parameterized to 3 at the moment). The suggested usage model is: "base page", "current page" and "overlay page". This way the API is fully compatible with existing programs, as they will continue to run on the "current page".

For example, you can now show:

  • Base layer: background controls that appear on every "screen"
  • Current layer: the main page in use, perhaps showing one particular view of a tabbed dialog
  • Overlay layer: popup dialogs, alerts, data entry boxes, etc.

Example ex24 showing a popup alert on top of a tabbed dialog:
image

After carefully reviewing the great proposals discussed in this issue and testing out the alternate implementations, I decided to proceed with this approach as it appears to deliver the biggest functionality gains for users with almost zero impact to the existing API. While I would prefer to revise the naming applied to "pages", this would have implied a large API naming change. I believe the current implementation will still provide much of the capability that @espHorst has described above (though we don't use relative coordinates).

Along with this update, the initial ability to create popup dialog boxes is now available. To show a popup dialog, one creates a "page" for it as before, then simply:

  • gslc_PopupShow(&m_gui,E_PG_ALERT,true);

This release also supports modal and modeless dialogs (as controlled by a parameter in PopupShow()

Lastly, there is also optional support for enabling each page layer to receive touch events and for enabling background redraw. The latter is useful to prevent updates to underlying pages from "bleeding through" to a popup, for example. If the popup is not placed over dynamically updating controls, then background updates can be enabled allowing other dynamically updated controls to continue to be drawn.

Again, a huge thank-you to @espHorst @Grtschnk and @Pconti31 for all of the feedback and suggestions on this feature!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants