diff --git a/README.md b/README.md index 9738b9c..106e36a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,39 @@ # cfGUI A simple GUI library for M5Stack (ESP32) -This library is intended to run on M5Stack development board (http://www.m5stack.com/). However, it should be easy to port it to any other board. +This library is intended to run on M5Stack development board (http://www.m5stack.com/), base on ESP32. + +However, it should be easy to port it to any other board with an LCD screen and 3 physical buttons. + It's composed of a simple hierarchy of graphical widget that can be drawn on screen. The following widgets have been created so far: - - Screen + - Screen and AppScreen (screen with a top bar and a bottom bar) - Bar - - StatusBar (with uptime, current time and wifi signal) - - Button - - Mosaic of widge - -![Example picture of cfGUI running on a M5Stack board](https://mastodon.codingfield.com/media/oXYl3M6SVqcpn2iefrs) + - StatusBar (Bar with uptime, clock and Wifi signal) + - ButtonInfoBar (display the function of the physical button) + - Button, UpDownButton + - Mosaic of widgets +![Example picture of cfGUI running on a M5Stack board](https://mastodon.codingfield.com/system/media_attachments/files/000/207/740/original/dbacf24f45561e5c.jpg) + +# How To (Platform.io) +To use this library with your platform.io project, simply clone or download the library into the directory 'lib' of your project. +Then, you just need to include the headers (e.g. #include ) and write some code. + +Look at examples if you need some inspiration ;-) # Todo - - Remove hard-coded values to make the lib more flexible + - Remove hard-coded values to make the lib more flexible - Add new widgets - Better 'focus' management +# Changelog +## 0.2.0 + - Change default font (looks better) + - New widgets (AppScreen, StatusBar, ButtonInfoBar, UpDownButton) + - Improved mosaic (the size and position of the widget is not hard-coded anymore) + - Global improvements + +## 0.1.0 + - First version of the library, with basic functionalities diff --git a/examples/simpleExample/main.cpp b/examples/simpleExample/main.cpp index 908212b..b5cf699 100644 --- a/examples/simpleExample/main.cpp +++ b/examples/simpleExample/main.cpp @@ -1,45 +1,58 @@ #include +#include #include -#include +#include #include #include #include +#include +#include +#include +#include +#include using namespace Codingfield::UI; -Screen screen(Size(320, 240), BLACK); +AppScreen* screen; StatusBar* topBar; -Bar* bottomBar; -Codingfield::UI::Button* button0; // M5STack should really use namespaces to avoid name clashes +ButtonInfoBar* bottomBar; +Codingfield::UI::Button* button0; Codingfield::UI::Button* button1; -Codingfield::UI::Button* button2; +Codingfield::UI::UpDownButton* button2; Codingfield::UI::Button* button3; Codingfield::UI::Button* button4; Codingfield::UI::Button* button5; WidgetMosaic* mosaic; Widget* focus; +int32_t editButtonValue = 0; +int32_t editOldButtonValue = 0; + void setup() { M5.begin(); - topBar = new StatusBar(&screen, Point(0,0), 25); - bottomBar = new Bar(&screen, Point(0, screen.GetSize().height-25), 25); + // Instanciate and configure all widgets + topBar = new StatusBar(); + bottomBar = new ButtonInfoBar(); + mosaic = new WidgetMosaic(3, 2); + screen = new AppScreen(Size(320, 240), BLACK, topBar, bottomBar, mosaic); - mosaic = new WidgetMosaic(&screen, Point(5,30), Size(320, 190)); - focus = mosaic; + // Give the focus to the main screen + focus = screen; button0 = new Codingfield::UI::Button(mosaic); button0->SetBackgroundColor(BLUE); button0->SetTextColor(WHITE); button0->SetText("16C"); + button0->SetTitle("Fridge"); button1 = new Codingfield::UI::Button(mosaic); button1->SetBackgroundColor(ORANGE); button1->SetTextColor(BLACK); button1->SetText("50%"); - button2 = new Codingfield::UI::Button(mosaic); + button2 = new Codingfield::UI::UpDownButton(mosaic); // Up/Down button button2->SetBackgroundColor(YELLOW); button2->SetTextColor(BLACK); - button2->SetText("3h15"); + button2->SetText("0"); button3 = new Codingfield::UI::Button(mosaic); button3->SetBackgroundColor(PURPLE); button3->SetTextColor(WHITE); @@ -56,7 +69,63 @@ void setup() { topBar->SetUptime(0); topBar->SetWifiStatus(StatusBar::WifiStatuses::No_signal); - screen.Draw(); + bottomBar->SetButtonAText("<"); + bottomBar->SetButtonBText("SELECT"); + bottomBar->SetButtonCText(">"); + + // Callback called by the mosaic when it changes mode (mosaic/zoom on 1 widget) + // We use it to update the bottom bar. + mosaic->SetZoomOnSelectedCallback([bottomBar](Widget* widget, bool edit) { + if(edit) { + if(widget->IsEditable()){ + bottomBar->SetButtonAText("-"); + bottomBar->SetButtonBText("APPLY"); + bottomBar->SetButtonCText("+"); + } else { + bottomBar->SetButtonAText(""); + bottomBar->SetButtonBText("BACK"); + bottomBar->SetButtonCText(""); + } + } else { + bottomBar->SetButtonAText("<"); + bottomBar->SetButtonBText("SELECT"); + bottomBar->SetButtonCText(">"); + } + }); + + // Configure callback to be called when the user wants to increment the value + // of button2 + button2->SetUpCallback([&editButtonValue](UpDownButton* w) { + editButtonValue++; + w->SetText(String(editButtonValue).c_str()); + return true; + }); + + // Configure callback to be called when the user wants to decrement the value + // of button2 + button2->SetDownCallback([&editButtonValue](UpDownButton* w) { + editButtonValue--; + w->SetText(String(editButtonValue).c_str()); + return true; + }); + + // Configure callback to be called when the user wants to apply the value + // of button2 + button2->SetApplyCallback([&editButtonValue, &editOldButtonValue](UpDownButton* w) { + editOldButtonValue = editButtonValue; + return false; + }); + + // Configure callback to be called when the user wants to cancel modification + // of the value of button2 + button2->SetCancelCallback([&editButtonValue](UpDownButton* w) { + editButtonValue = editOldButtonValue; + w->SetText(String(editButtonValue).c_str()); + return true; + }); + + // Draw the screen and all its children + screen->Draw(); } uint32_t loopCount = 0; @@ -64,16 +133,16 @@ uint32_t temperature = 10; uint32_t percent = 0; std::string state = "BUSY"; int32_t wifiStatusIndex; -int rssi = -100; -int seconds = 0; std::vector wifiStatus {StatusBar::WifiStatuses::No_signal, StatusBar::WifiStatuses::Weak, StatusBar::WifiStatuses::Medium, StatusBar::WifiStatuses::Full}; int32_t uptimeHours=0; +bool longPush = false; void loop() { M5.update(); + // Update values displayd on the screen (status bar, buttons,...) if((loopCount % 50) == 0) { if(temperature < 100) temperature++; @@ -94,43 +163,53 @@ void loop() { topBar->SetUptime(uptimeHours); char strftime_buf[64]; - if(seconds < 60) - seconds++; - else - seconds = 0; - snprintf(strftime_buf, 64, "%02d:%02d:%02d", 1, 2, seconds); + snprintf(strftime_buf, 64, "%02d:%02d:%02d", 12, 14, 59); topBar->SetDateTime(strftime_buf); - - if(rssi < 0) - rssi++; - else - rssi = -100; - if(rssi >= -55) - topBar->SetWifiStatus(StatusBar::WifiStatuses::Full); - else if(rssi >= -75) - topBar->SetWifiStatus(StatusBar::WifiStatuses::Medium); - else if(rssi >= -85) - topBar->SetWifiStatus(StatusBar::WifiStatuses::Weak); - else - topBar->SetWifiStatus(StatusBar::WifiStatuses::No_signal); } if((loopCount % 100) == 0) { if(state == "BUSY") state = "IDLE"; else state = "BUSY"; button5->SetText(state); + + auto rssi =WiFi.RSSI(); + if(rssi >= -55) { + topBar->SetWifiStatus(StatusBar::WifiStatuses::Full); + } else if(rssi >= -75) { + topBar->SetWifiStatus(StatusBar::WifiStatuses::Medium); + } else if(rssi >= -85) { + topBar->SetWifiStatus(StatusBar::WifiStatuses::Weak); + } else { + topBar->SetWifiStatus(StatusBar::WifiStatuses::No_signal); + } } - if(M5.BtnA.wasPressed()) + // Notify the widgets that physical buttons are pressed + if(M5.BtnA.wasPressed()) { focus->OnButtonAPressed(); + } - if(M5.BtnB.wasPressed()) - focus->OnButtonBPressed(); + if(M5.BtnB.pressedFor(1000)) { + if(!longPush) { + focus->OnButtonBLongPush(); + longPush = true; + } + } + else if(M5.BtnB.wasReleased()) { + if(!longPush) { + focus->OnButtonBPressed(); + } + else { + longPush = false; + } + } - if(M5.BtnC.wasPressed()) + if(M5.BtnC.wasPressed()) { focus->OnButtonCPressed(); + } - screen.Draw(); + // Redraw the screen + screen->Draw(); loopCount++; delay(10); diff --git a/library.properties b/library.properties index 964f4e6..b3a9e6a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=cfGUI -version=0.1 +version=0.2.0 author=Jean-François Milants maintainer=Jean-François Milants sentence=cfGUI diff --git a/src/AppScreen.cpp b/src/AppScreen.cpp new file mode 100644 index 0000000..27c6c71 --- /dev/null +++ b/src/AppScreen.cpp @@ -0,0 +1,59 @@ +#include "AppScreen.h" +#include + +using namespace Codingfield::UI; + +AppScreen::AppScreen(Size size, Color color) : Screen(size, color) { + +} + +AppScreen::AppScreen(Size size, Color color, Bar* topBar, Bar* bottomBar, Widget* centreWidget) : Screen(size, color), topBar{topBar}, bottomBar{bottomBar}, centreWidget{centreWidget} { + if(topBar != nullptr) { + topBar->SetParent(this); + topBar->SetPosition(Point(0,0)); + topBar->SetSize(Size(this->GetSize().width, barHeight)); + AddChild(topBar); + } + + if(bottomBar != nullptr) { + bottomBar->SetParent(this); + bottomBar->SetPosition(Point(0, this->GetSize().height-barHeight)); + bottomBar->SetSize(Size(this->GetSize().width, barHeight)); + AddChild(bottomBar); + } + + if(centreWidget != nullptr) { + Point centerPosition; + centerPosition.y = position.y + ((topBar != nullptr) ? topBar->GetSize().height : 0) + padding; + centerPosition.x = position.x + padding; + + Size centerSize; + centerSize.height = size.height - (((bottomBar != nullptr) ? bottomBar->GetSize().height : 0) + ((topBar != nullptr) ? topBar->GetSize().height : 0) + padding); + centerSize.width = size.width; + + centreWidget->SetParent(this); + centreWidget->SetPosition(centerPosition); + centreWidget->SetSize(centerSize); + AddChild(centreWidget); + } +} + +void AppScreen::OnButtonAPressed() { + if(centreWidget != nullptr) + centreWidget->OnButtonAPressed(); +} + +void AppScreen::OnButtonBPressed() { + if(centreWidget != nullptr) + centreWidget->OnButtonBPressed(); +} + +void AppScreen::OnButtonBLongPush() { + if(centreWidget != nullptr) + centreWidget->OnButtonBLongPush(); +} + +void AppScreen::OnButtonCPressed() { + if(centreWidget != nullptr) + centreWidget->OnButtonCPressed(); +} diff --git a/src/AppScreen.h b/src/AppScreen.h new file mode 100644 index 0000000..da0a2b0 --- /dev/null +++ b/src/AppScreen.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Codingfield { + namespace UI { + class Bar; + class AppScreen : public Screen { + public: + AppScreen(Size size, Color color); + AppScreen(Size size, Color color, Bar* topBar, Bar* bottomBar, Widget* centreWidget); + + virtual void OnButtonAPressed() override; + virtual void OnButtonBPressed() override; + virtual void OnButtonBLongPush() override; + virtual void OnButtonCPressed() override; + + private: + Bar* topBar = nullptr; + Bar* bottomBar = nullptr; + Widget* centreWidget = nullptr; + int32_t padding = 5; + int32_t barHeight = 25; + }; + } +} diff --git a/src/Bar.cpp b/src/Bar.cpp index 91df31f..d793cf5 100644 --- a/src/Bar.cpp +++ b/src/Bar.cpp @@ -3,6 +3,7 @@ using namespace Codingfield::UI; void Bar::Draw() { + if(IsHidden()) return; if(isUpdated) M5.Lcd.fillRect(position.x, position.y, size.width, size.height, WHITE); diff --git a/src/Bar.h b/src/Bar.h index 2dfde13..d83fd17 100644 --- a/src/Bar.h +++ b/src/Bar.h @@ -6,6 +6,7 @@ namespace Codingfield { namespace UI { class Bar : public Widget { public: + Bar() : Widget(nullptr, Point(), Size()) {} Bar(Widget* parent, Point position, int32_t height) : Widget(parent, position, Size(parent->GetSize().width, height)) {} void Draw() override; private: diff --git a/src/Button.cpp b/src/Button.cpp index 086b87b..a10d2ad 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -1,4 +1,5 @@ #include "Button.h" +#include "Free_Fonts.h" using namespace Codingfield::UI; @@ -23,26 +24,43 @@ void Codingfield::UI::Button::SetTextColor(Color c) { void Codingfield::UI::Button::SetSelected(bool s) { if(isSelected != s) { isSelected = s; - isUpdated = true; + SetUpdateFlag(); } } void Codingfield::UI::Button::SetText(const std::string& t) { if(text != t) { this->text = t; - isUpdated = true; + SetUpdateFlag(); + } +} + +void Codingfield::UI::Button::SetTitle(const std::string& t) { + if(title != t) { + title = t; + SetUpdateFlag(); } } void Codingfield::UI::Button::Draw() { + if(IsHidden()) return; if(isUpdated) { M5.Lcd.setTextColor(textColor); - M5.Lcd.setTextSize(2); M5.Lcd.fillRect(position.x, position.y, size.width, size.height, backgroundColor); M5.Lcd.setTextDatum(MC_DATUM); M5.Lcd.setTextColor(textColor); - M5.Lcd.drawString(text.c_str(), position.x + (size.width/2), position.y + (size.height/2)); + if(title.empty()) { + M5.Lcd.setFreeFont(FF22); + M5.Lcd.drawString(text.c_str(), position.x + (size.width/2), position.y + (size.height/2)); + M5.Lcd.setFreeFont(FF21); + } + else { + M5.Lcd.setFreeFont(FF22); + M5.Lcd.drawString(text.c_str(), position.x + (size.width/2), position.y + (size.height/3)); + M5.Lcd.setFreeFont(FF21); + M5.Lcd.drawString(title.c_str(), position.x + (size.width/2), position.y + ((size.height/3)*2)); + } if(isSelected) { M5.Lcd.drawRect(position.x, position.y, size.width, size.height, RED); diff --git a/src/Button.h b/src/Button.h index 767300e..b49b05d 100644 --- a/src/Button.h +++ b/src/Button.h @@ -12,11 +12,13 @@ namespace Codingfield { void SetTextColor(Color c); virtual void SetSelected(bool s) override; void SetText(const std::string& t); + void SetTitle(const std::string& t); void Draw() override; - private: + protected: Color backgroundColor = BLACK; Color textColor = BLACK; std::string text; + std::string title; }; } diff --git a/src/ButtonInfoBar.cpp b/src/ButtonInfoBar.cpp new file mode 100644 index 0000000..67e36c9 --- /dev/null +++ b/src/ButtonInfoBar.cpp @@ -0,0 +1,44 @@ +#include "ButtonInfoBar.h" + +using namespace Codingfield::UI; + +void ButtonInfoBar::SetButtonAText(const std::string& t) { + if(btnAText != t) { + btnAText = t; + isUpdated = true; + } +} + +void ButtonInfoBar::SetButtonBText(const std::string& t) { + if(btnBText != t) { + btnBText = t; + isUpdated = true; + } +} + +void ButtonInfoBar::SetButtonCText(const std::string& t) { + if(btnCText != t) { + btnCText = t; + isUpdated = true; + } +} + +void ButtonInfoBar::Draw() { + if(IsHidden()) return; + bool oldIsUpdated = isUpdated; + Bar::Draw(); + + if(oldIsUpdated) { + M5.Lcd.setTextColor(BLACK); + + M5.Lcd.setTextDatum(TC_DATUM); + M5.Lcd.drawString(btnAText.c_str(), (size.width/6), position.y + 5); + + M5.Lcd.setTextDatum(TC_DATUM); + M5.Lcd.drawString(btnBText.c_str(), size.width/2, position.y + 5); + + M5.Lcd.setTextDatum(TC_DATUM); + M5.Lcd.drawString(btnCText.c_str(), ((size.width/3)*2) + (size.width/6), position.y + 5); + } + isUpdated = false; +} diff --git a/src/ButtonInfoBar.h b/src/ButtonInfoBar.h new file mode 100644 index 0000000..890fe4c --- /dev/null +++ b/src/ButtonInfoBar.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Bar.h" + +namespace Codingfield { + namespace UI { + class ButtonInfoBar : public Bar { + public: + ButtonInfoBar() : Bar() {} + ButtonInfoBar(Widget* parent, Point position, int32_t height) : Bar(parent, position, height) {} + void Draw() override; + void SetButtonAText(const std::string& t); + void SetButtonBText(const std::string& t); + void SetButtonCText(const std::string& t); + private: + Color color = WHITE; + std::string btnAText; + std::string btnBText; + std::string btnCText; + }; + } +} diff --git a/src/Free_Fonts.h b/src/Free_Fonts.h new file mode 100644 index 0000000..d701aec --- /dev/null +++ b/src/Free_Fonts.h @@ -0,0 +1,377 @@ +// Attach this header file to your sketch to use the GFX Free Fonts. You can write +// sketches without it, but it makes referencing them easier. + +// This calls up ALL the fonts but they only get loaded if you actually +// use them in your sketch. +// +// No changes are needed to this header file unless new fonts are added to the +// library "Fonts/GFXFF" folder. +// +// To save a lot of typing long names, each font can easily be referenced in the +// sketch in three ways, either with: +// +// 1. Font file name with the & in front such as &FreeSansBoldOblique24pt7b +// an example being: +// +// tft.setFreeFont(&FreeSansBoldOblique24pt7b); +// +// 2. FF# where # is a number determined by looking at the list below +// an example being: +// +// tft.setFreeFont(FF32); +// +// 3. An abbreviation of the file name. Look at the list below to see +// the abbreviations used, for example: +// +// tft.setFreeFont(FSSBO24) +// +// Where the letters mean: +// F = Free font +// M = Mono +// SS = Sans Serif (double S to distinguish is form serif fonts) +// S = Serif +// B = Bold +// O = Oblique (letter O not zero) +// I = Italic +// # = point size, either 9, 12, 18 or 24 +// +// Setting the font to NULL will select the GLCD font: +// +// tft.setFreeFont(NULL); // Set font to GLCD + +#define LOAD_GFXFF + +#ifdef LOAD_GFXFF // Only include the fonts if LOAD_GFXFF is defined in User_Setup.h + +// Use these when printing or drawing text in GLCD and high rendering speed fonts +#define GFXFF 1 +#define GLCD 0 +#define FONT2 2 +#define FONT4 4 +#define FONT6 6 +#define FONT7 7 +#define FONT8 8 + +// Use the following when calling setFont() +// +// Reserved for GLCD font // FF0 +// + +#define TT1 &TomThumb + +#define FM9 &FreeMono9pt7b +#define FM12 &FreeMono12pt7b +#define FM18 &FreeMono18pt7b +#define FM24 &FreeMono24pt7b + +#define FMB9 &FreeMonoBold9pt7b +#define FMB12 &FreeMonoBold12pt7b +#define FMB18 &FreeMonoBold18pt7b +#define FMB24 &FreeMonoBold24pt7b + +#define FMO9 &FreeMonoOblique9pt7b +#define FMO12 &FreeMonoOblique12pt7b +#define FMO18 &FreeMonoOblique18pt7b +#define FMO24 &FreeMonoOblique24pt7b + +#define FMBO9 &FreeMonoBoldOblique9pt7b +#define FMBO12 &FreeMonoBoldOblique12pt7b +#define FMBO18 &FreeMonoBoldOblique18pt7b +#define FMBO24 &FreeMonoBoldOblique24pt7b + +#define FSS9 &FreeSans9pt7b +#define FSS12 &FreeSans12pt7b +#define FSS18 &FreeSans18pt7b +#define FSS24 &FreeSans24pt7b + +#define FSSB9 &FreeSansBold9pt7b +#define FSSB12 &FreeSansBold12pt7b +#define FSSB18 &FreeSansBold18pt7b +#define FSSB24 &FreeSansBold24pt7b + +#define FSSO9 &FreeSansOblique9pt7b +#define FSSO12 &FreeSansOblique12pt7b +#define FSSO18 &FreeSansOblique18pt7b +#define FSSO24 &FreeSansOblique24pt7b + +#define FSSBO9 &FreeSansBoldOblique9pt7b +#define FSSBO12 &FreeSansBoldOblique12pt7b +#define FSSBO18 &FreeSansBoldOblique18pt7b +#define FSSBO24 &FreeSansBoldOblique24pt7b + +#define FS9 &FreeSerif9pt7b +#define FS12 &FreeSerif12pt7b +#define FS18 &FreeSerif18pt7b +#define FS24 &FreeSerif24pt7b + +#define FSI9 &FreeSerifItalic9pt7b +#define FSI12 &FreeSerifItalic12pt7b +#define FSI19 &FreeSerifItalic18pt7b +#define FSI24 &FreeSerifItalic24pt7b + +#define FSB9 &FreeSerifBold9pt7b +#define FSB12 &FreeSerifBold12pt7b +#define FSB18 &FreeSerifBold18pt7b +#define FSB24 &FreeSerifBold24pt7b + +#define FSBI9 &FreeSerifBoldItalic9pt7b +#define FSBI12 &FreeSerifBoldItalic12pt7b +#define FSBI18 &FreeSerifBoldItalic18pt7b +#define FSBI24 &FreeSerifBoldItalic24pt7b + +#define FF0 NULL //ff0 reserved for GLCD +#define FF1 &FreeMono9pt7b +#define FF2 &FreeMono12pt7b +#define FF3 &FreeMono18pt7b +#define FF4 &FreeMono24pt7b + +#define FF5 &FreeMonoBold9pt7b +#define FF6 &FreeMonoBold12pt7b +#define FF7 &FreeMonoBold18pt7b +#define FF8 &FreeMonoBold24pt7b + +#define FF9 &FreeMonoOblique9pt7b +#define FF10 &FreeMonoOblique12pt7b +#define FF11 &FreeMonoOblique18pt7b +#define FF12 &FreeMonoOblique24pt7b + +#define FF13 &FreeMonoBoldOblique9pt7b +#define FF14 &FreeMonoBoldOblique12pt7b +#define FF15 &FreeMonoBoldOblique18pt7b +#define FF16 &FreeMonoBoldOblique24pt7b + +#define FF17 &FreeSans9pt7b +#define FF18 &FreeSans12pt7b +#define FF19 &FreeSans18pt7b +#define FF20 &FreeSans24pt7b + +#define FF21 &FreeSansBold9pt7b +#define FF22 &FreeSansBold12pt7b +#define FF23 &FreeSansBold18pt7b +#define FF24 &FreeSansBold24pt7b + +#define FF25 &FreeSansOblique9pt7b +#define FF26 &FreeSansOblique12pt7b +#define FF27 &FreeSansOblique18pt7b +#define FF28 &FreeSansOblique24pt7b + +#define FF29 &FreeSansBoldOblique9pt7b +#define FF30 &FreeSansBoldOblique12pt7b +#define FF31 &FreeSansBoldOblique18pt7b +#define FF32 &FreeSansBoldOblique24pt7b + +#define FF33 &FreeSerif9pt7b +#define FF34 &FreeSerif12pt7b +#define FF35 &FreeSerif18pt7b +#define FF36 &FreeSerif24pt7b + +#define FF37 &FreeSerifItalic9pt7b +#define FF38 &FreeSerifItalic12pt7b +#define FF39 &FreeSerifItalic18pt7b +#define FF40 &FreeSerifItalic24pt7b + +#define FF41 &FreeSerifBold9pt7b +#define FF42 &FreeSerifBold12pt7b +#define FF43 &FreeSerifBold18pt7b +#define FF44 &FreeSerifBold24pt7b + +#define FF45 &FreeSerifBoldItalic9pt7b +#define FF46 &FreeSerifBoldItalic12pt7b +#define FF47 &FreeSerifBoldItalic18pt7b +#define FF48 &FreeSerifBoldItalic24pt7b + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +// Now we define "s"tring versions for easy printing of the font name so: +// tft.println(sFF5); +// will print +// Mono bold 9 +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +#define sFF0 "GLCD" +#define sTT1 "Tom Thumb" +#define sFF1 "Mono 9" +#define sFF2 "Mono 12" +#define sFF3 "Mono 18" +#define sFF4 "Mono 24" + +#define sFF5 "Mono bold 9" +#define sFF6 "Mono bold 12" +#define sFF7 "Mono bold 18" +#define sFF8 "Mono bold 24" + +#define sFF9 "Mono oblique 9" +#define sFF10 "Mono oblique 12" +#define sFF11 "Mono oblique 18" +#define sFF12 "Mono oblique 24" + +#define sFF13 "Mono bold oblique 9" +#define sFF14 "Mono bold oblique 12" +#define sFF15 "Mono bold oblique 18" +#define sFF16 "Mono bold oblique 24" // Full text line is too big for 480 pixel wide screen + +#define sFF17 "Sans 9" +#define sFF18 "Sans 12" +#define sFF19 "Sans 18" +#define sFF20 "Sans 24" + +#define sFF21 "Sans bold 9" +#define sFF22 "Sans bold 12" +#define sFF23 "Sans bold 18" +#define sFF24 "Sans bold 24" + +#define sFF25 "Sans oblique 9" +#define sFF26 "Sans oblique 12" +#define sFF27 "Sans oblique 18" +#define sFF28 "Sans oblique 24" + +#define sFF29 "Sans bold oblique 9" +#define sFF30 "Sans bold oblique 12" +#define sFF31 "Sans bold oblique 18" +#define sFF32 "Sans bold oblique 24" + +#define sFF33 "Serif 9" +#define sFF34 "Serif 12" +#define sFF35 "Serif 18" +#define sFF36 "Serif 24" + +#define sFF37 "Serif italic 9" +#define sFF38 "Serif italic 12" +#define sFF39 "Serif italic 18" +#define sFF40 "Serif italic 24" + +#define sFF41 "Serif bold 9" +#define sFF42 "Serif bold 12" +#define sFF43 "Serif bold 18" +#define sFF44 "Serif bold 24" + +#define sFF45 "Serif bold italic 9" +#define sFF46 "Serif bold italic 12" +#define sFF47 "Serif bold italic 18" +#define sFF48 "Serif bold italic 24" + +#else // LOAD_GFXFF not defined so setup defaults to prevent error messages + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +// Free fonts are not loaded in User_Setup.h so we must define all as font 1 +// to prevent compile error messages +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +#define GFXFF 1 +#define GLCD 1 +#define FONT2 2 +#define FONT4 4 +#define FONT6 6 +#define FONT7 7 +#define FONT8 8 + +#define FF0 1 +#define FF1 1 +#define FF2 1 +#define FF3 1 +#define FF4 1 +#define FF5 1 +#define FF6 1 +#define FF7 1 +#define FF8 1 +#define FF9 1 +#define FF10 1 +#define FF11 1 +#define FF12 1 +#define FF13 1 +#define FF14 1 +#define FF15 1 +#define FF16 1 +#define FF17 1 +#define FF18 1 +#define FF19 1 +#define FF20 1 +#define FF21 1 +#define FF22 1 +#define FF23 1 +#define FF24 1 +#define FF25 1 +#define FF26 1 +#define FF27 1 +#define FF28 1 +#define FF29 1 +#define FF30 1 +#define FF31 1 +#define FF32 1 +#define FF33 1 +#define FF34 1 +#define FF35 1 +#define FF36 1 +#define FF37 1 +#define FF38 1 +#define FF39 1 +#define FF40 1 +#define FF41 1 +#define FF42 1 +#define FF43 1 +#define FF44 1 +#define FF45 1 +#define FF46 1 +#define FF47 1 +#define FF48 1 + +#define FM9 1 +#define FM12 1 +#define FM18 1 +#define FM24 1 + +#define FMB9 1 +#define FMB12 1 +#define FMB18 1 +#define FMB24 1 + +#define FMO9 1 +#define FMO12 1 +#define FMO18 1 +#define FMO24 1 + +#define FMBO9 1 +#define FMBO12 1 +#define FMBO18 1 +#define FMBO24 1 + +#define FSS9 1 +#define FSS12 1 +#define FSS18 1 +#define FSS24 1 + +#define FSSB9 1 +#define FSSB12 1 +#define FSSB18 1 +#define FSSB24 1 + +#define FSSO9 1 +#define FSSO12 1 +#define FSSO18 1 +#define FSSO24 1 + +#define FSSBO9 1 +#define FSSBO12 1 +#define FSSBO18 1 +#define FSSBO24 1 + +#define FS9 1 +#define FS12 1 +#define FS18 1 +#define FS24 1 + +#define FSI9 1 +#define FSI12 1 +#define FSI19 1 +#define FSI24 1 + +#define FSB9 1 +#define FSB12 1 +#define FSB18 1 +#define FSB24 1 + +#define FSBI9 1 +#define FSBI12 1 +#define FSBI18 1 +#define FSBI24 1 + +#endif // LOAD_GFXFF diff --git a/src/Screen.cpp b/src/Screen.cpp index a694762..9deaf1f 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -1,8 +1,13 @@ #include "Screen.h" +#include "Free_Fonts.h" using namespace Codingfield::UI; void Screen::Draw() { + M5.Lcd.setFreeFont(FF21); + M5.Lcd.setTextSize(1); + + if(IsHidden()) return; if(isUpdated) M5.Lcd.fillScreen(color); diff --git a/src/StatusBar.cpp b/src/StatusBar.cpp index 44b8557..df8ff4a 100644 --- a/src/StatusBar.cpp +++ b/src/StatusBar.cpp @@ -10,31 +10,31 @@ extern const uint8_t image_data_wifi3[]; void StatusBar::SetWifiStatus(const StatusBar::WifiStatuses status) { if(wifiStatus != status) { wifiStatus = status; - isUpdated = true; + SetUpdateFlag(); } } void StatusBar::SetUptime(const uint32_t t) { if(uptime != t) { uptime = t; - isUpdated = true; + SetUpdateFlag(); } } void StatusBar::SetDateTime(const std::string& t) { if(dateTime != t) { dateTime = t; - isUpdated = true; + SetUpdateFlag(); } } void StatusBar::Draw() { + if(IsHidden()) return; bool oldIsUpdated = isUpdated; Bar::Draw(); if(oldIsUpdated) { M5.Lcd.setTextColor(BLACK); - M5.Lcd.setTextSize(2); M5.Lcd.setTextDatum(TL_DATUM); String s = String("UP:") + String(uptime) + String("h"); diff --git a/src/StatusBar.h b/src/StatusBar.h index f489d28..ecb05d8 100644 --- a/src/StatusBar.h +++ b/src/StatusBar.h @@ -7,11 +7,13 @@ namespace Codingfield { class StatusBar : public Bar { public: enum class WifiStatuses {No_signal, Weak, Medium, Full}; + StatusBar() : Bar() {} StatusBar(Widget* parent, Point position, int32_t height) : Bar(parent, position, height) {} void Draw() override; void SetWifiStatus(const WifiStatuses status); void SetUptime(const uint32_t t); void SetDateTime(const std::string& t); + private: Color color = WHITE; WifiStatuses wifiStatus; diff --git a/src/UpDownButton.cpp b/src/UpDownButton.cpp new file mode 100644 index 0000000..0caee4f --- /dev/null +++ b/src/UpDownButton.cpp @@ -0,0 +1,75 @@ +#include "UpDownButton.h" +#include "Free_Fonts.h" +using namespace Codingfield::UI; + +UpDownButton::UpDownButton(Widget* parent) : Button(parent) { + isEditable = true; +} + +void UpDownButton::Draw() { + if(IsHidden()) return; + + if(isUpdated) { + Button::Draw(); + if(controlsEnabled) { + M5.Lcd.setTextDatum(MC_DATUM); + M5.Lcd.setTextColor(textColor); + M5.Lcd.drawString("-", position.x + (size.width/6), position.y + (size.height/2)); + M5.Lcd.drawString("+", position.x + (size.width - (size.width/6)), position.y + (size.height/2)); + } + } + + isUpdated = false; +} + +void UpDownButton::EnableControls() { + controlsEnabled = true; +} + +void UpDownButton::DisableControls() { + controlsEnabled = false; +} + +void UpDownButton::OnButtonAPressed() { + if(upCallback != nullptr) { + if(downCallback(this)) + SetUpdateFlag(); + } +} + +void UpDownButton::OnButtonBPressed() { + if(applyCallback != nullptr) { + if(applyCallback(this)) + SetUpdateFlag(); + } +} + +void UpDownButton::OnButtonBLongPush() { + if(cancelCallback != nullptr) { + if(cancelCallback(this)) + SetUpdateFlag(); + } +} + +void UpDownButton::OnButtonCPressed() { + if(upCallback != nullptr) { + if(upCallback(this)) + SetUpdateFlag(); + } +} + +void UpDownButton::SetUpCallback(std::function callback) { + upCallback = callback; +} + +void UpDownButton::SetDownCallback(std::function callback) { + downCallback = callback; +} + +void UpDownButton::SetApplyCallback(std::function callback) { + applyCallback = callback; +} + +void UpDownButton::SetCancelCallback(std::function callback) { + cancelCallback = callback; +} diff --git a/src/UpDownButton.h b/src/UpDownButton.h new file mode 100644 index 0000000..5104780 --- /dev/null +++ b/src/UpDownButton.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Button.h" + +namespace Codingfield { + namespace UI { + class UpDownButton : public Button { + public: + UpDownButton(Widget* parent); + virtual void Draw(); + + virtual void EnableControls() override; + virtual void DisableControls() override; + + virtual void OnButtonAPressed() override; + virtual void OnButtonBPressed() override; + virtual void OnButtonBLongPush() override; + virtual void OnButtonCPressed() override; + + void SetUpCallback(std::function callback); + void SetDownCallback(std::function callback); + void SetApplyCallback(std::function callback); + void SetCancelCallback(std::function callback); + + private: + bool controlsEnabled = false; + int32_t currentValue = 0; + int32_t oldValue = 0; + + std::function upCallback = nullptr; + std::function downCallback = nullptr; + std::function applyCallback = nullptr; + std::function cancelCallback = nullptr; + }; + } +} diff --git a/src/Widget.cpp b/src/Widget.cpp index c94f49b..59b0ce8 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -1,12 +1,29 @@ #include "Widget.h" + using namespace Codingfield::UI; Widget::Widget(Widget* parent) : Widget(parent, Point(0,0), Size(0,0)) { - } Widget::Widget(Widget* parent, Point position, Size size) : parent{parent}, position{position}, size {size} { if(parent != nullptr) parent->AddChild(this); } + +void Widget::SetUpdateFlag() { + for(auto c : children) { + c->SetUpdateFlag(); + } + isUpdated = true; +} + +void Widget::SetSize(const Size& s) { + size = s; + OnSizeUpdated(); +} + +void Widget::SetPosition(const Point& p) { + position = p; + OnPositionUpdated(); +} diff --git a/src/Widget.h b/src/Widget.h index efea336..512c2d4 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -7,39 +7,70 @@ namespace Codingfield { namespace UI { using Color = uint32_t; + + /* Base class for all widgets + * A widget is an object that has a parent widget, children, a position and a size, + * and is able to draw itself on the screen.*/ class Widget { public: Widget(Widget* parent); Widget(Widget* parent, Point position, Size size); - Widget(Point position, Size size) : Widget(nullptr, position, size) {} + void SetParent(Widget* parent) { this->parent = parent; } const Size& GetSize() const { return size; } - void SetSize(const Size& s) { size = s; } + void SetSize(const Size& s); const Point& GetPosition() const {return position; } - void SetPosition(const Point& p) {position = p; } + void SetPosition(const Point& p); + + /* The widget will be visible (drawn) the next time Draw() will be called */ + void Show() { SetUpdateFlag(); isVisible = true; } + + /* The widget will not be visible (not drawn) the next time Draw() will be called */ + void Hide() { SetUpdateFlag(); isVisible = false; } + + bool IsVisible() const { return isVisible;} + bool IsHidden() const { return !isVisible;} + + /* Returns true if the value of the widget can be edited via buttons */ + bool IsEditable() const { return isEditable; } + /* Add a widget as a child of this one */ virtual void AddChild(Widget* widget) { children.push_back(widget); } + /* Draw the widget. Implemented by concrete implementations of Widget */ virtual void Draw() = 0; + /* These methods must be called by the application when buttons are pressed + * They allow the implementation of Widget to react to user actions on + * physical buttons. */ virtual void OnButtonAPressed() { } - virtual void OnButtonBPressed() { } - + virtual void OnButtonBLongPush() { } virtual void OnButtonCPressed() { } + /* This method must be called by the application to specify is this instance + * of Widget is selected or not. */ virtual void SetSelected(bool s) { } + /* Enables/disables controls on the button */ + virtual void EnableControls() {} + virtual void DisableControls() {} + protected: + virtual void OnSizeUpdated() { } + virtual void OnPositionUpdated() { } + void SetUpdateFlag(); + Point position; Size size; Widget* parent = nullptr; std::vector children; bool isSelected = false; bool isUpdated = true; + bool isVisible = true; + bool isEditable = false; }; - } } diff --git a/src/WidgetMosaic.cpp b/src/WidgetMosaic.cpp index 8a99a26..39a283c 100644 --- a/src/WidgetMosaic.cpp +++ b/src/WidgetMosaic.cpp @@ -1,46 +1,145 @@ #include "WidgetMosaic.h" - +#include using namespace Codingfield::UI; -WidgetMosaic::WidgetMosaic(Widget* parent, Point position, Size size) : Widget(parent, position, size) { +WidgetMosaic::WidgetMosaic(Widget* parent, Point position, Size size, int32_t nbColumns, int32_t nbRows) : Widget(parent, position, size), + nbColumns {nbColumns}, + nbRows {nbRows} { + +} + +WidgetMosaic::WidgetMosaic(int32_t nbColumns, int32_t nbRows) : WidgetMosaic(nullptr, Point(), Size(), nbColumns, nbRows) { } void WidgetMosaic::Draw() { - for(Widget* w : children) - w->Draw(); + if(IsHidden()) return; + if(isUpdated) { + M5.Lcd.fillRect(position.x, position.y, size.width, size.height, BLACK); + } + + if(!zoomOnSelected) { + for(Widget* w : children) + w->Draw(); + } + else { + selectedWidget->Draw(); + } + + isUpdated = false; } void WidgetMosaic::AddChild(Widget* widget) { Widget::AddChild(widget); - widget->SetSize(Size(100,88)); - switch(children.size()) { - case 1: widget->SetPosition(Point(5,30)); break; - case 2: widget->SetPosition(Point(110,30)); break; - case 3: widget->SetPosition(Point(215,30)); break; - case 4: widget->SetPosition(Point(5,122)); break; - case 5: widget->SetPosition(Point(110,122)); break; - case 6: widget->SetPosition(Point(215,122)); break; - default: break; + + Size widgetSize = ComputeWidgetSize(); + widget->SetSize(widgetSize); + + int32_t position = ((children.size()-1) % (nbRows*nbColumns)); + widget->SetPosition(ComputeWidgetPosition(widgetSize, position)); + SetUpdateFlag(); +} + +const Widget* WidgetMosaic::GetSelected() const { + return selectedWidget; +} + +void WidgetMosaic::ZoomOnSelected(bool enabled) { + bool oldValue = zoomOnSelected; + if(selectedWidget != nullptr && enabled) { + zoomOnSelected = true; + Size widgetSize = ComputeWidgetSize(1,1); + selectedWidget->SetSize(widgetSize); + selectedWidget->SetPosition(ComputeWidgetPosition(widgetSize, 0)); + if(selectedWidget->IsEditable()) { + selectedWidget->EnableControls(); + } + } else { + zoomOnSelected = false; + + for(auto index = 0; index < children.size(); index++) { + Widget* widget = children.at(index); + Size widgetSize = ComputeWidgetSize(); + widget->SetSize(widgetSize); + widget->SetPosition(ComputeWidgetPosition(widgetSize, index)); + if(selectedWidget->IsEditable()) { + selectedWidget->DisableControls(); + } + } + } + + if(oldValue != zoomOnSelected) { + zoomOnSelectedCallback(selectedWidget, zoomOnSelected); + SetUpdateFlag(); } } void WidgetMosaic::OnButtonAPressed() { + if(zoomOnSelected) + return selectedWidget->OnButtonAPressed(); + if(indexSelected > 0) { children[indexSelected]->SetSelected(false); indexSelected--; children[indexSelected]->SetSelected(true); + selectedWidget = children[indexSelected]; } } void WidgetMosaic::OnButtonBPressed() { + if(zoomOnSelected) { + ZoomOnSelected(false); + selectedWidget->OnButtonBPressed(); + return; + } + ZoomOnSelected(true); +} + +void WidgetMosaic::OnButtonBLongPush() { + if(zoomOnSelected) + selectedWidget->OnButtonBLongPush(); + ZoomOnSelected(false); } void WidgetMosaic::OnButtonCPressed() { + if(zoomOnSelected) + return selectedWidget->OnButtonCPressed(); + if(indexSelected < children.size()-1) { children[indexSelected]->SetSelected(false); indexSelected++; children[indexSelected]->SetSelected(true); + selectedWidget = children[indexSelected]; } } + +Size WidgetMosaic::ComputeWidgetSize() { + return ComputeWidgetSize(nbColumns, nbRows); +} + +Size WidgetMosaic::ComputeWidgetSize(int32_t nbColumns, int32_t nbRows) { + Size widgetSize; + widgetSize.width = (size.width - ((2*border) + ((nbColumns-1)*border))) / nbColumns; + widgetSize.height = (size.height - ((2*border) + ((nbRows-1)*border))) / nbRows; + return widgetSize; +} + +Point WidgetMosaic::ComputeWidgetPosition(const Size& widgetSize, int32_t position) { + Point widgetPosition; + + int32_t row = ((int32_t)position / (int32_t)nbColumns); + int32_t column = position % nbColumns; + widgetPosition.x = ((column)*border) + (column * widgetSize.width) + this->position.x; + widgetPosition.y = ((row)*border) + (row * widgetSize.height) + this->position.y; + + return widgetPosition; +} + +bool WidgetMosaic::IsZoomOnSelected() const { + return zoomOnSelected; +} + +void WidgetMosaic::SetZoomOnSelectedCallback(std::functionfunc) { + zoomOnSelectedCallback = func; +} diff --git a/src/WidgetMosaic.h b/src/WidgetMosaic.h index b7a133d..bddb63a 100644 --- a/src/WidgetMosaic.h +++ b/src/WidgetMosaic.h @@ -1,19 +1,36 @@ #pragma once #include "Widget.h" +#include "Point.h" namespace Codingfield { namespace UI { class WidgetMosaic : public Widget { public: - WidgetMosaic(Widget* parent, Point position, Size size); + WidgetMosaic(int32_t nbColumns = 3, int32_t nbRows = 2); + WidgetMosaic(Widget* parent, Point position, Size size, int32_t nbColumns = 3, int32_t nbRows = 2); void Draw() override; virtual void AddChild(Widget* widget) override; virtual void OnButtonAPressed() override; virtual void OnButtonBPressed() override; + virtual void OnButtonBLongPush() override; virtual void OnButtonCPressed() override; + const Widget* GetSelected() const; + bool IsZoomOnSelected() const; + void SetZoomOnSelectedCallback(std::function func); private: int32_t indexSelected = 0; + int32_t border = 5; + int32_t nbRows; + int32_t nbColumns; + + Size ComputeWidgetSize(); + Size ComputeWidgetSize(int32_t nbColumns, int32_t nbRows); + Point ComputeWidgetPosition(const Size& widgetSize, int32_t position); + Widget* selectedWidget = nullptr; + bool zoomOnSelected = false; + void ZoomOnSelected(bool enabled); + std::function zoomOnSelectedCallback = nullptr; }; } }