TFT_eSPI
TFT_eSPI copied to clipboard
Add "actions" to buttons, allowing custom callbacks to be attached to buttons
Hey, great library! Again, thank you for making it!
I am currently writing another application using it, and I will have a series of buttons with various actions for each button. I believe that attaching a function to the button object itself results in good application of DRY techniques and makes adding more buttons a breeze - once the core is in place.
Now the specifics are not too important, but to demonstrate the usage of this PR please see below:
Note, the code is an incomplete example - setting up the screen etc are assumed to be complete already.
TFT_eSPI_Button btnL, btnR;
TFT_eSPI_Button *buttons[] = { &btnL, &btnR };
uint8_t buttonCount = sizeof(buttons) / sizeof(buttons[0]);
void initButtons() {
btnL.initButtonUL(&tft, tft.width() - 105, tft.height() - 30, 50, 30, TFT_WHITE, TFT_WHITE, TFT_BLACK, const_cast<char *>("<-"), 1);
btnL.setButtonAction([]() {
Serial.println("BUTTON LEFT PRESSED");
});
btnL.drawButton();
btnR.initButtonUL(&tft, tft.width() - 50, tft.height() - 30, 50, 30, TFT_WHITE, TFT_WHITE, TFT_BLACK, const_cast<char *>("->"), 1);
btnR.setButtonAction([]() {
Serial.println("BUTTON RIGHT PRESSED");
});
btnR.drawButton();
}
// Then later on, in your UI loop with touch handler:
void loop() {
tft.setCursor(0, 30, 2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
int pos[2] = { 0, 0 };
ft6236_pos(pos);
tft.print("Touch X: ");tft.print(pos[0]);tft.print(" Y: ");tft.println(pos[1]);tft.print(" ");
for (uint8_t buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) {
TFT_eSPI_Button *btn = buttons[buttonIndex];
if (pos[0] != -1 && pos[1] != -1) {
btn->press(btn->contains(tft.width() - pos[0], pos[1]));
} else {
btn->press(false);
}
// In this example, i invert buttons when pressed and trigger the "action"
if (btn->justReleased()) {
btn->drawButton(false);
} else if (btn->justPressed()) {
btn->action();
btn->drawButton(true);
}
}
}
Please let me know what else you wish to see implemented or changed in the PR - it's your project after all! 😄
The button class is a bit too basic at the moment so extra functionality would be of benefit and simplify sketches.
My initial thoughts need more consideration but are:
-
Three action callback types are needed to cover the majority of cases:
- just pressed action, e.g. turn on an LED, invert the button and/or change the state of something
- pressed and held action, e.g. to brighten an LED gradually depending on how long the button is pressed
- just released action, to revert button inversion and/or revert to previous state of something
-
New button functions are needed that make a GUI simpler to implement, the above actions can be optionally attached:
- check boxes, e.g. blank or x in box (can be automated within library and getState used to retrieve state
- push buttons, visually get inverted when first pressed, revert when released
- toggle buttons, button invert state toggles on each press
- New button getState function added, needs thought, maybe with a bit field returned:
- bit 0 = button in inverted/ not inverted
- bit 1 = check box checked/not checked
- bit 2 = just pressed/not just pressed
- bit 3 = pressed and held/not pressed
- bit 4 = just released/not just released
Examples would be needed that are easy to understand for a noob. Generally it is best not to use DRY philosophy in the basic examples so that the library functions are demonstrated as simply as possible for each use case, however for more sophisticated examples like a working calculator this would be OK.
Have thought a bit more about this. It would be simpler to have one action callback as you suggest, then within that callback a user can call getState and decide what to do with the press state.
Also, at the moment the contains(x,y) function is a bit clumsy, so maybe wrap up this function within a checkButton(x, y) call, then the action gets called if the x,y are in the button box. The function should return a status flag (set in the callback function) so calling code knows if an action was taken.
Hey, great ideas!
I might give this a go over the next few weeks if you don't beat me to it 😄 I'll probably have more thoughts as the implementation progresses.
I have thought further on this and concluded that an independant buttons support library is the way to go. This then reduces the maintenance, development and bug/question/documentation support load on myself.
I can quickly create a template library for you with all the existing functions if that helps.
Sure, sounds good! Thanks!
I actually opened a discussion about buttons yesterday and hadn't seen this PR. I think this is an excellent path to go. I'm sure you've seen it but GUIslice has a button callback (see here for quick look)
@jack828 Nice to see someone else using a CTP and working on something similar. Let me know if there's anything I can do to help.
While continuing my research I ran across this repo, https://github.com/davetcc/IoAbstraction, which handles touch events for both resistive and capacitive screens, as well as other devices, in an interesting way.
I have created a draft library here: https://github.com/Bodmer/TFT_eButton
It is working but has a few rough edges. The example needs work but demonstrates:
- A toggle button
- A button that inverts every 1s, pressing the button stops the flashing for 10s
- Pressing the buttons also produces various serial messages
- Smooth anti-aliased rectangle use
- Free font as label
The buttons can use the anti-aliased (smooth) rounded rectangle to give an improved appearance.
Clearly all this is too much for a beginner in one sketch, so I will break out some simpler examples.
Constructive comments welcome.
I was just drafting up something similar so thanks for beating me! I just took a quick look and haven't had my coffee so don't judge me too harshly.
- Is there a reason why one of the defined button states isn't
HELD
- I need to look at how you use
setPressTime
andsetReleaseTime
I was originally thinking of adding a parameter that lets you define how long before the button state is changed toHELD
- Side note, do you think you'll ever include support for capacitive touch panels?
I'll experiment with your example a little later on and provide some feedback for you but thanks again for working on this.
- Is there a reason why one of the defined button states isn't
HELD
Nope, just a test example for a momentary, non-latching push button. This is not a library button operation mode, it is coded in the button action.- I need to look at how you use
setPressTime
andsetReleaseTime
I was originally thinking of adding a parameter that lets you define how long before the button state is changed toHELD
That is all it is, it is for experimentation as the library could capture this time.- Side note, do you think you'll ever include support for capacitive touch panels? No, because I think a separate library is a better option to cope with CTP chip features like multi-touch, swipe, pinch etc. The touch library built into TFT_eSPI will be deprecated soon and eventually may be removed. I created one here for my SSD1963 TFT.
I'll experiment with your example a little later on and provide some feedback for you but thanks again for working on this.
Thanks. The original Adafruit button library was simplistic as it was for limited resource processors like the UNO, so could do with a revamp.
I have an unpublished graphical widgets library that contains sliders, battery charge indicators etc so maybe I ought to add the button capability to it as well. The smooth graphics functions were developed specifically for that, but it is a WIP.
I have some questions on your example, would you rather I start a thread over there?
@Bodmer I did a little playing around and it's almost embarrassing to share but take a look at https://github.com/GeorgeIoak/TFT_eButton. I added a new longPressAction and added my example sketch that uses it. It needs cleanup but basically my thoughts are with something like this you can add control for things that need to respond to long button holds. In my case it was temperature control, another case would be volume control, scrolling, etc.
...
btnUP.setLongPressAction(btnCommonAction);
...
for (uint8_t b = 0; b < buttonCount; b++) {
if (pressed) {
if (btn[b]->contains(t_x, t_y)) {
btn[b]->press(true);
btn[b]->longPressAction(b, 1000, 5); // btn#, long press time, long press increment
}
}
else {
btn[b]->press(false);
btn[b]->releaseAction();
}
}
and then the callback looks like this:
void btnCommonAction(uint8_t btnNum, uint8_t longPressTime, uint8_t longPressInc)
{
if (btn[btnNum]->justPressed()) {
btn[btnNum]->drawSmoothButton(true);
btn[btnNum]->setPressTime(millis());
}
// if button pressed for more than 1 sec...
if (millis() - btn[btnNum]->getPressTime() >= longPressTime) {
Serial.println("Button pressed for 1 second.......");
btn[btnNum]->setPressTime(millis());
switch (btnNum) {
case 0: // UP Button
if (desiredTemp < MAX_H2O_TEMP)
desiredTemp += longPressInc;
else
desiredTemp = MAX_H2O_TEMP;
break;
case 1: // DOWN Button
if (desiredTemp > MIN_H2O_TEMP)
desiredTemp -= longPressInc;
else
desiredTemp = MIN_H2O_TEMP;
break;
default:
break;
}
}
tft.setTextColor(TFT_BLUE, TFT_BLACK);
tft.drawNumber(desiredTemp, tft.width()/2, ((tft.height()/2)-180), 8);
}
I just wanted to get something working at first but my thoughts were to add a button state of HELD
and then allow the option of passing in the time required to be considered held and then a default increment to use when in the HELD
state. There's many ways to handle this so it might get too complicated (for instance after you wait the allotted time to enter into the HELD
state do you respond immediately or do you wait before incrementing. Maybe all that is handled in actual callback instead of baking in something)?
Just thinking out loud to see what others think...
I will think on this further.
I am wondering if a new action is needed. Have you tried the original eBotton example sketch and looked at the serial outputs? The pressAction of the on/off button code does 3 things, the last one seems to be exactly what you need. Try "press and holding" the "on/off button", the 3 things the callback handles are:
- button toggle
- still pressing reports for 1 seconds
- after the 1 second timeout it reports "Stop pressing my buttton......." as long as it remains pressed
Is that what you need?
I did play with your example and it's what I based my code on. It was actually close to what I wanted so maybe my thoughts are just not as mainstream as I thought they were. What I was thinking is that when you define a button you should be able to "just" be able to have a HELD state defined based on a parameter that lets you define how long the button must be touched before it is considered HELD.
Your callbacks didn't allow anything to passed in so that's why I created the new function. I wouldn't consider myself a programmer so I may have approached this in the wrong way or maybe you're right and what you've created handles 99% of the use cases and I'm just that odd one!
In my code I chose to wait for the held period before reacting to the continued hold, many times (like in IR remotes) once you hold the button the repeat just blasts out as fast as possible. I kind of thought of it like a rotary dial with acceleration capability so it starts slow and then speeds up the response (not implemented in my code because I have a small control window).
Screen buttons share a lot of common action requirements to physical switch buttons, so I have been looking at this library which seems popular: https://github.com/LennartHennigs/Button2
I think screen and physical button functions can have a very similar API once the screen zone is specified. Then the button type is abstracted and both screen and physical buttons can be supported within the same library. There will be variations since I would like to specify the image icons for the buttons but the action handling would be very similar.
1000% agree with you and that library looks exactly of what I was thinking. Yes, I also agree that in many projects you will have both physical and screen buttons (as well as possibly ESP capacitive buttons) and that library looks like it handles most use cases well.
I didn't look into how it was implemented but one of the attractions to GUIslice (for me) what that the builder allowed creating image buttons which makes perfect sense for a TFT.
I've got an urgent (paid for) hardware project to do now so I will not be able to spend any time developing the TFT_eButton library further for quite a while. I think it addresses the original request from @jack828 for callbacks. The other features discussed would require significant effort. I will pop back to this topic now and then and if I have any "light bulb" moments!
Thanks for letting up know and good luck on the project. I'm up to my eyeballs as well but if there's anything I can do to help feel free to ask. I'll be plugging away at this as well so if I make any great revolutions I'll post a comment.
I have ported a library from Kris Kasprzak that seems to work quite well. Copy here: https://github.com/Bodmer/ILI9341_t3_controls It has a few quirks but it produces some nice looking functional screens. All the examples should work OK, if touch is required then your screen will need to be calibrated. The sketch for this is included as an example.
You're supposed to be working on that hardware project!
I'm up to my eyeballs but I'll definitely give this a try when I get a chance as it looks interesting.
Hey folks. Sorry for the radio silence, I've been someplace sunny. I'm also up to the eyeballs in stuff but I'm very glad to see all this progress on the idea. It's quite likely that I'll not have time to go back to personal projects for a few weeks, but, if I do then this'll take priority.
I think most of these ideas are now built into the TFT_eWidget library and the Button_demo example.