/** * ScreenUi * A toolkit for building character based user interfaces on small displays. * Copyright (c) 2012 Jason von Nieda * * This file is part of ScreenUi. * * ScreenUi is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ScreenUi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ScreenUi. If not, see . */ #ifndef __ScreenUi_h__ #define __ScreenUi_h__ #include //#define SCREENUI_DEBUG 1 #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) class Screen; // Represents a set of characters that can be mapped to a continuous sequence // of integers starting with 0. class CharSet { public: // Returns true if the character set contains the given character. virtual bool contains(unsigned char ch) { return indexOf(ch) != -1; } // Returns the index in the character set for the given character. // Return -1 if the character is not part of the character set. virtual int indexOf(unsigned char ch); // Returns the character at the given index for the character set. // Return -1 if the index is not within the range of the character set. virtual int charAt(int index) = 0; // Returns the length of the character set. This method can be used // to determine what the maximum index for charAt() will be. virtual unsigned char size() = 0; }; // An implementation of CharSet that uses several ranges to determine it's // full character set class RangeCharSet : public CharSet { public: RangeCharSet(int rangeCount, ...); virtual ~RangeCharSet(); virtual int charAt(int index); virtual unsigned char size(); private: unsigned char rangeCount_; unsigned char *ranges_; }; extern RangeCharSet defaultCharSet; extern RangeCharSet floatingPointCharSet; class Component { public: Component() { x_ = y_ = width_ = height_ = 0; } // Set the location on screen for this component. x and y are zero based, // absolute character positions. virtual void setLocation(int8_t x, int8_t y) { x_ = x; y_ = y;} // Set the width and height this component. Most components will either // require a width and height to be specified during creation or will // adopt sane defaults based on their input data. void setSize(uint8_t width, uint8_t height) { width_ = width; height_ = height; } int8_t x() { return x_; } int8_t y() { return y_; } uint8_t width() { return width_; } uint8_t height() { return height_; } // Returns true if the component is willing to accept focus from the focus // subsystem. For a component to receive input events it must be willing // to accept focus. virtual bool acceptsFocus() { return false; } // The first step in the component update cycle. This is called by Screen // during it's update cycle to allow each component to reset or set up // any data that needs to be modified from the last update cycle. virtual void update(Screen *screen) {} // Called if the component has focus and is selected. x and y are delta // since the last event. // Returns true if the component wishes to remain selected. Returns false // to give up selection. virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled) { return false; } // The final step in the component update cycle. Called by Screen to allow // the component to draw itself on screen. It shoud generally draw itself // at it's location and should not overflow it's size. // Currently components are responsible for drawing their own focus // indicator. This may change in the future. // Component::paint() sets dirty to false and should be called by every // subclass's paint method. virtual void paint(Screen *screen); // Returns true if the component is a container for other components. This // is a shortcut so that we don't have to do RTTI when iterating over a // list of components looking for containers. virtual bool isContainer() { return false; } // Returns true if the Component is marked dirty and needs to be painted // on the next update. virtual bool dirty(); // Sets dirty to true for this Component, causing it to be painted during // the next update. // TODO refactor the two below into setDirty() virtual void repaint() { dirty_ = true; } virtual void clearDirty() { dirty_ = false; } #ifdef SCREENUI_DEBUG virtual char *description() { return "Component"; } #endif protected: int8_t x_, y_; uint8_t width_, height_; bool dirty_; }; // A Component that contains other Components. Users should not generally // create instances of this class. class Container : public Component { public: Container(); virtual ~Container(); virtual void add(Component *component, int8_t x, int8_t y); virtual void update(Screen *screen); // Paints any dirty child components. virtual void paint(Screen *screen); virtual bool isContainer() { return true; } // Returns true if any child components are dirty. virtual bool dirty(); // Sets dirty to true for all child components, causing them to be repainted // during the next update. virtual void repaint(); #ifdef SCREENUI_DEBUG virtual char *description() { return "Container"; } #endif virtual bool contains(Component *component); protected: Component *nextFocusHolder(Component *focusHolder, bool reverse); Component *nextFocusHolder(Component *focusHolder, bool reverse, bool *focusHolderFound); void offsetChildren(int x, int y); Component **components_; uint8_t componentsLength_; uint8_t componentCount_; bool firstUpdateCompleted_; }; // The main entry point into the ScreenUi system. A Screen instance represents // a full screen of data on the display, including modifiable Components and // provides methods for input and output. // The user should create instances of Screen to manage a user interface and // add() Components to the screen. // When a Screen is ready to be displayed, the user should call update() in // a loop. After each call to update(), Components can be queried for their // data. class Screen : public Container { public: Screen(uint8_t width, uint8_t height); // Should be called regularly by the main program to update the Screen // and process input. After each call to update(), each Component // will have processed any input it received and will have updated it // data. virtual void update(); // Returns the current focus holder for the Screen. The focus holder is // the component that input events will be sent to. Component *focusHolder() { return focusHolder_; } // Sets the current focus holder. This can be used to set the default // button on a screen before it is displayed, for instance. void setFocusHolder(Component *focusHolder) { focusHolder_ = focusHolder; } void setCursorLocation(uint8_t x, uint8_t y) { cursorX_ = x; cursorY_ = y; } #ifdef SCREENUI_DEBUG virtual char *description() { return "Screen"; } #endif // The following methods must be overridden by the user to provide // hardware support // Get any changes in the input since the last call to update(); virtual void getInputDeltas(int *x, int *y, bool *selected, bool *cancelled); // Clear the screen virtual void clear(); virtual void createCustomChar(uint8_t slot, uint8_t *data); virtual void draw(uint8_t x, uint8_t y, const char *text); virtual void draw(uint8_t x, uint8_t y, uint8_t customChar); virtual void setCursorVisible(bool visible); virtual void setBlink(bool blink); virtual void moveCursor(uint8_t x, uint8_t y); private: bool cleared_; Component *focusHolder_; bool focusHolderSelected_; bool annoyingBugWorkedAround_; uint8_t cursorX_, cursorY_; }; // A Component that displays static text at a specific position. class Label : public Component { public: Label(const char *text); virtual const char *text() { return (const char *) text_; } virtual void setText(const char *text); virtual void paint(Screen *screen); #ifdef SCREENUI_DEBUG virtual char *description() { return "Label"; } #endif protected: char* text_; bool captured_; uint8_t dirtyWidth_; }; // A Component that can receive focus and select events. If the Button has // focus when the user presses the select button the Button's pressed() property // is set indicating that the button was selected. // Button is a subclass of Label and thus displays itself as text. class Button : public Label { public: Button(const char *text); virtual bool acceptsFocus() { return true; } bool pressed() { return pressed_; } virtual void update(Screen *screen); virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled); #ifdef SCREENUI_DEBUG virtual char *description() { return "Button"; } #endif private: bool pressed_; }; // A Component that displays either an on or off state. Clicking the component // while it has focus causes it to change from on to off or vice-versa. class Checkbox : public Label { public: Checkbox(); bool checked() { return checked_; } virtual bool acceptsFocus() { return true; } virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled); #ifdef SCREENUI_DEBUG virtual char *description() { return "Checkbox"; } #endif private: bool checked_; }; // A Component that allows the user to scroll through several choices and // select one. When the List is selected, future scroll events will cause it // to scroll through it's selections. A select sets the current item // or a cancel resets the list to it's previously selected item and // releases control. class List : public Label { public: List(uint8_t maxItems); virtual ~List(); void addItem(const char *item); const char *selectedItem() { return items_[selectedIndex_]; } uint8_t selectedIndex() { return selectedIndex_; } void setSelectedIndex(uint8_t selectedIndex); virtual bool acceptsFocus() { return true; } virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled); #ifdef SCREENUI_DEBUG virtual char *description() { return "List"; } #endif private: char **items_; uint8_t itemCount_; uint8_t selectedIndex_; }; // A Component that allows the user to scroll through a range of Integers // or floats. class Spinner : public Label { public: Spinner(int value, int low, int high, int increment, bool rollover); int intValue(); virtual bool acceptsFocus() { return true; } virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled); #ifdef SCREENUI_DEBUG virtual char *description() { return "Spinner"; } #endif private: char buffer_[10]; int value_, low_, high_, increment_; bool rollover_; }; // allows text input. Each character can be clicked to scroll through the alphabet. class Input : public Label { public: Input(char *text); virtual void setText(char *text); virtual bool acceptsFocus() { return true; } virtual void paint(Screen *screen); virtual bool handleInputEvent(int x, int y, bool selected, bool cancelled); #ifdef SCREENUI_DEBUG virtual char *description() { return "Input"; } #endif void setCharSet(CharSet *charSet) { charSet_ = charSet; } CharSet *charSet() { return charSet_; } protected: int8_t position_; bool selecting_; CharSet *charSet_; }; // A Component that accepts input of a floating or fixed point decimal // number. class DecimalInput : public Input { }; // A Component that accepts input of an integer value with a given base. class IntegerInput : public Input { public: // Create a signed IntegerInput with the given value, width and base. // If width is 0 the width will be determined by the number of digits in // the incoming value, with one additional space for the negative. // If base is not specified, the default is base 10. IntegerInput(long value, unsigned char width = 0, unsigned char base = 10); // Create a signed IntegerInput with the given value, width and base. // If width is 0 the width will be determined by the number of digits in // the incoming value. // If base is not specified, the default is base 10. IntegerInput(unsigned long value, unsigned char width = 0, unsigned char base = 10); private: bool signed_; unsigned char base_; }; // Component that allows the user to enter a time with up to three fields // separated by semi-colons. e.g. 00, 00:00, 00:00:00 class TimeInput : public Input { }; // A Container that allows the user to scroll through any number of rows of // Components. The ScrollContainer can be thought of as a Screen with the same // width as the Container it is added to, and an unlimited height. // Components that will be added to the ScrollContainer should have their // location set relative to their position in the ScrollContainer, not // the main Screen. class ScrollContainer : public Container { public: // We require a reference to Screen because we need focus information // during the dirty() check. This is a bit of a dirty hack, but it's // better than adding this property to every other object or walking // the tree to find it. ScrollContainer(Screen *screen, uint8_t width, uint8_t height); virtual ~ScrollContainer(); virtual void paint(Screen *screen); virtual bool dirty(); #ifdef SCREENUI_DEBUG virtual char *description() { return "ScrollContainer"; } #endif private: bool scrollNeeded(); Component *lastFocusHolder_; Screen *screen_; char *clearLine; }; // A specialization of ScrollContainer that contains only Buttons and provides // a simple API for managing the set of Buttons like a menu. class Menu : public ScrollContainer { }; #endif