diff --git a/README.md b/README.md index 87dab56..2aa1dbf 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,20 @@ [![Build Status](https://travis-ci.org/local-projects/ofxInterface.svg?branch=master)](https://travis-ci.org/local-projects/ofxInterface) [![Build status](https://ci.appveyor.com/api/projects/status/u1m369pj4mmr8xvu?svg=true)](https://ci.appveyor.com/project/armadillu/ofxinterface) flexible and lightweight GUI helper with a scene-graph and a multitouch manager for OpenFrameworks + +Compatible with openFrameworks 0.11.0+ + +Generate all project files using the openFrameworks Project Generator. + +## Features + +* tool to create a touch focused interface +* includes standard components like button and keyboard (see [definitions](https://github.com/brinoausrino/ofxInterface/blob/master/components.md)) +* create a GUI from json + +## Required Addons + +* [ofxAnimatable](https://github.com/armadillu/ofxAnimatable) +* [ofxAssetManager](https://github.com/brinoausrino/ofxAssetManager) +* [ofxEasing](https://github.com/arturoc/ofxEasing) +* [ofxFontStash2](https://github.com/armadillu/ofxFontStash2) diff --git a/addon_config.mk b/addon_config.mk new file mode 100644 index 0000000..9cdd6f2 --- /dev/null +++ b/addon_config.mk @@ -0,0 +1,72 @@ +# All variables and this file are optional, if they are not present the PG and the +# makefiles will try to parse the correct values from the file system. +# +# Variables that specify exclusions can use % as a wildcard to specify that anything in +# that position will match. A partial path can also be specified to, for example, exclude +# a whole folder from the parsed paths from the file system +# +# Variables can be specified using = or += +# = will clear the contents of that variable both specified from the file or the ones parsed +# from the file system +# += will add the values to the previous ones in the file or the ones parsed from the file +# system +# +# The PG can be used to detect errors in this file, just create a new project with this addon +# and the PG will write to the console the kind of error and in which line it is + +meta: + ADDON_NAME = ofxInterface + ADDON_DESCRIPTION = flexible and lightweight GUI helper with a scene-graph and a multitouch manager + ADDON_AUTHOR = galsasson, armadillu, brinoausrino + ADDON_TAGS = "sceneManager" "gui" "multitouch" + ADDON_URL = https://github.com/brinoausrino/ofxInterface + +common: + # dependencies with other addons, a list of them separated by spaces + # or use += in several lines + ADDON_DEPENDENCIES = ofxAnimatable + ADDON_DEPENDENCIES += ofxAssetManager + ADDON_DEPENDENCIES += ofxEasing + ADDON_DEPENDENCIES += ofxFontStash2 + + # include search paths, this will be usually parsed from the file system + # but if the addon or addon libraries need special search paths they can be + # specified here separated by spaces or one per line using += + # ADDON_INCLUDES += + + # any special flag that should be passed to the compiler when using this + # addon + # ADDON_CFLAGS + + # any special flag that should be passed to the linker when using this + # addon, also used for system libraries with -lname + # ADDON_LDFLAGS = + + # linux only, any library that should be included in the project using + # pkg-config + # ADDON_PKG_CONFIG_LIBRARIES = + + # osx/iOS only, any framework that should be included in the project + # ADDON_FRAMEWORKS = + + # source files, these will be usually parsed from the file system looking + # in the src folders in libs and the root of the addon. if your addon needs + # to include files in different places or a different set of files per platform + # they can be specified here + # ADDON_SOURCES = + + # some addons need resources to be copied to the bin/data folder of the project + # specify here any files that need to be copied, you can use wildcards like * and ? + # ADDON_DATA = + + # when parsing the file system looking for libraries exclude this for all or + # a specific platform + # ADDON_LIBS_EXCLUDE = + + +linux64: +linux: +linuxarmv6l: +linuxarmv7l: +android/armeabi: +android/armeabi-v7a: diff --git a/components.drawio b/components.drawio new file mode 100644 index 0000000..a9c1ee2 --- /dev/null +++ b/components.drawio @@ -0,0 +1 @@ +7ZpNU9swEIZ/TY7t+CtfxxJCSwsMM2mHclTiJTYoXo8ik5hfXxlLsRWFGBoc+5AT1nplye/uI8lLOu5osf7OSBxcow+041j+uuOedxzHsXqO+JNZ0txi246bW+Ys9KWtMEzCF5BGS1qT0Iel5sgRKQ9j3TjDKIIZ12yEMVzpbg9I9VFjMgfDMJkRalrvQp8HuXXg9Av7DwjngRrZ7g3zOwuinOWbLAPi46pkcscdd8QQeX61WI+AZuopXfJ+F2/c3UyMQcTf0+H+5/z6xud/LvE8PCOPtv3ij7/IYDwTmsgXlpPlqVIAfCGIbCLjAc4xInRcWM8YJpEP2TCWaBU+V4ixMNrC+AicpzK6JOEoTAFfUHkXIv9bFivRjDCC3HIRUiofueSEceUxpTh7UsaSUz7xbLZvCiRNS0zYDPaoohKNsDnwPX7OJowCAMAFcJaKfgwo4eGzPg8iE3G+8StiJS5kuD4QOtsIHT7cCACNAOrhWQUhh0lMXgVYCWr1UMiHAuOw3q+j+d6yg6e6KOYtScCqAMhWVAQleHpWTVI5hlQ7haqQRtfxEKGkMJuloSTMsHdMYbqtwF/DWy4A23SXlgi1AOhrxCfi7x0J/9eu4q1IWnKIMYz4svTk28xQ0KVyQeXQcGvV/5C7uMjHL1Jo8yL/n1WekVUjpMhuSZSdDppanaQC7g7ojrsa9U7Q7VCl/xkwGenvWPpe5Ay2oppDLnsVgf0olcY4FVhW+NfDZd9IvN+w5gmDVhwbbE+XpHlQBydQd6gyrAVU1zsOqMY4FaBW+NcD6tBIvCsybXLvVKvWoG2IqjLBiVFdljfieCCk3uA4kBrjVEBa4V8PpLb5AX6NfkJhTGEBUoA2fYe3gFa3CTiFXCz9m/X/2lXN+/K98/UG0ayVdnZUndoAtVzaKr9LD6b/sCCb1ZbsmHkZxUnzTLhd/XvYcxtnotsIE+uQl5AQrXuV0eK6ACJrtJYHr6ZNTk8R952b3KftK2b5ZBIuYgqjAGZPwBpnyBu2jqFTSWWnLPXUVHqOHv+6ToHGOFWlzv3+NZ0CzaLKZMaQUjIVxGLESRi1gNnuVh24BWfBU3Vlpyz1lFf62/8HqIlZY5wKZiv8a2LWrK9M8IH/gnSKhPmNw9q32garGuwEqy5LPWWWoXUcWI1xKmCt8K8HVscss0xo6LdgTx14x8NUNItfCuXSFj+4csf/AA== \ No newline at end of file diff --git a/components.md b/components.md new file mode 100644 index 0000000..6f3c8f7 --- /dev/null +++ b/components.md @@ -0,0 +1,143 @@ +# ofxInterface - components + +![the components](components.png "Interface base components") + +## Properties + +### Node + +The base element. + +| property | type | description | +| ------------- |---------------| -----| +| id | string | unique identifier (should always be set) | +| position | ofVec2f | position in px| +| size | ofVec2f | size in px | +| plane | float | the bigger the more in front (default 10) | +| renderClip | bool | clip element parts out of element size | +| active | bool | set element visible and enabled | + +### ModalElement + +Element that describes buttons, checkers and radios. + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| type | string | ether "button","checker" or "radio" | +| colorActive | ofColor | color for activated state| +| colorInactive | ofColor | color for inactive state| +| colorSelected | ofColor | color for selected state| + +### TextInput + +An editable Text. + +Inherits from ModalElement. + +| property | type | description | +| ------------- |---------------| -----| +| font | string | id of the font | +| maxChars | int | maximum length of input| +| description | string | text when line empty| + +### SimpleChecker + +A simple checkbox ;) - + +Inherits from ModalElement. + +### ColorPanel + +A colored panel + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| strokeWidth | float | stroke width | +| borderRadius | float | border radius | +| color | ofColor | panel color | +| strokeColor | ofColor | border color | + +### TextureNode + +A Node that displays a texture + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| texture | string | texture id | +| tint | string | texture tinting (color id is set)| +| blendmode | string | "alpha","none","add","multiply","screen" and "subract" allowed | +| strokeColor | ofColor | border color | + +### Label + +A short text + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| font | string | id of the font | +| text | string | text to be drawn| +| alignment | string | "left","center" or "right"| +| shadow | object | activate dropshadow| + +#### shadow parameters +| property | type | description | +| ------------- |---------------| -----| +| size | float | shadow size (bigger is blurrier)| +| x | float | x-offset| +| y | float | y-offset| +| color | ofColor | shadow color | + +### ScrollableContainer + +A container to contain elements bigger than screen size. + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| sizeScrollableArea | ofVec2f | the scrollable area size | +| bgColor | ofColor | background color| +| scrollActiveColor | ofColor | scrollbar color when selected| +| scrollInactiveColor | ofColor | scrollbar color when not selected| + +### SoftKeyboard + +An on-screen keyboard. + +Inherits from Node. Height of Element depends on width and is automatically set. + +| property | type | description | +| ------------- |---------------| -----| +| font | string | id of the font | +| bgColor | ofColor | background color| +| colorActive | ofColor | key background color when enabled| +| colorInactive | ofColor | key background color when disabled| +| colorSelected | ofColor | key background color when selected| +| borderRadius | float | border radius (also effects keys)| +| borderWidth | float | border width (also effects keys)| +| margin | float | space between border and keys| +| padding | float | space between keys| + + +### TextureNode + +A simple Slider. + +Inherits from Node. + +| property | type | description | +| ------------- |---------------| -----| +| direction | string | "horizontal", "vertical" | +| colorActive | ofColor | key background color when enabled| +| colorInactive | ofColor | key background color when disabled| +| colorSelected | ofColor | key background color when selected| +| colorSelected | ofColor | key background color when selected| +| lineWidth | int | thickness of the bar| \ No newline at end of file diff --git a/components.png b/components.png new file mode 100644 index 0000000..baa8657 Binary files /dev/null and b/components.png differ diff --git a/example-advanced/addons.make b/example-advanced/addons.make index 51aff7e..a004113 100644 --- a/example-advanced/addons.make +++ b/example-advanced/addons.make @@ -1,2 +1,5 @@ +ofxAnimatable +ofxAssetManager +ofxEasing +ofxFontStash2 ofxInterface -ofxAnimatable \ No newline at end of file diff --git a/example-basic/addons.make b/example-basic/addons.make index 3f201f2..a004113 100644 --- a/example-basic/addons.make +++ b/example-basic/addons.make @@ -1 +1,5 @@ -ofxInterface \ No newline at end of file +ofxAnimatable +ofxAssetManager +ofxEasing +ofxFontStash2 +ofxInterface diff --git a/exampleJson/addons.make b/exampleJson/addons.make new file mode 100644 index 0000000..a004113 --- /dev/null +++ b/exampleJson/addons.make @@ -0,0 +1,5 @@ +ofxAnimatable +ofxAssetManager +ofxEasing +ofxFontStash2 +ofxInterface diff --git a/exampleJson/bin/data/.gitkeep b/exampleJson/bin/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/exampleJson/bin/data/assets.json b/exampleJson/bin/data/assets.json new file mode 100644 index 0000000..c29a992 --- /dev/null +++ b/exampleJson/bin/data/assets.json @@ -0,0 +1,18 @@ +{ + "textures":{ + "folder":"textures", + "info":"files [[id,path]]", + "files":[ + ["cat1","cat1.jpg"] + ] + }, + "fonts":{ + "folder":"fonts", + "info":"files [[id,path]]", + "files":[ + ["Verdana","verdana.ttf"], + ["Franklin Gothic","frabk.ttf"] + ] + } + +} \ No newline at end of file diff --git a/exampleJson/bin/data/fonts/frabk.ttf b/exampleJson/bin/data/fonts/frabk.ttf new file mode 100644 index 0000000..21c4ecf Binary files /dev/null and b/exampleJson/bin/data/fonts/frabk.ttf differ diff --git a/exampleJson/bin/data/fonts/verdana.ttf b/exampleJson/bin/data/fonts/verdana.ttf new file mode 100644 index 0000000..8f25a64 Binary files /dev/null and b/exampleJson/bin/data/fonts/verdana.ttf differ diff --git a/exampleJson/bin/data/gui.json b/exampleJson/bin/data/gui.json new file mode 100644 index 0000000..bd7aafb --- /dev/null +++ b/exampleJson/bin/data/gui.json @@ -0,0 +1,83 @@ +{ + "elements":[ + { + "id":"background", + "class":"colorPanel", + "color":"bg", + "size":[1280,768] + },{ + "id":"Head", + "class":"label", + "font":"Headline", + "position":[80,44], + "size":[1280,40], + "text":"GUI from json" + },{ + "id":"text", + "class":"textField", + "size":[820,40], + "position":[80,100], + "text":[ + "" + ] + },{ + "id":"button", + "class":"modalElement", + "position":[80,160], + "size":[32,32], + "colorActive":"black", + "colorSelected":"white", + "colorInactive":"black", + "info": "sub elements can be defined", + "elements":[ + { + "id":"description", + "class":"labelSmall", + "position":[40,10], + "text": "a button" + } + ] + },{ + "id":"checker", + "class":"simpleChecker", + "position":[250,160], + "size":[32,32], + "colorActive":"black", + "colorSelected":"white", + "colorInactive":"black", + "elements":[ + { + "id":"description", + "class":"labelSmall", + "position":[40,10], + "text": "a simpleChecker" + } + ] + }, + { + "id":"pic1", + "class":"texture", + "position":[950,44], + "texture":"cat1", + "size":[150,150] + } + ,{ + "id":"input", + "class":"textInput", + "position":[80,300], + "size":[300,50], + "font":"Input", + "maxChars":20, + "description":"write something" + },{ + "id":"keyboard", + "class":"softKeyboard", + "position":[80,380], + "size":[1000,500], + "font":"Keyboard" + + } + + ] +} \ No newline at end of file diff --git a/exampleJson/bin/data/guiElements.json b/exampleJson/bin/data/guiElements.json new file mode 100644 index 0000000..e649bee --- /dev/null +++ b/exampleJson/bin/data/guiElements.json @@ -0,0 +1,46 @@ +{ + "colors":[ + {"id":"black","color":[0,0,0]}, + {"id":"white","color":[255,255,255]}, + {"id":"bg","color":[30,30,30]}, + {"id":"tintRed","color":[250,0,0,120]}, + {"id":"inactiveSlider","color":[31,31,31]}, + {"id":"dropShadow","color":[0,0,0,50]}, + {"id":"iconBg","color":[31,31,31]} + ], + "fontStyles":[ + { + "name":"Headline", + "fontId":"Franklin Gothic", + "fontSize":40, + "color":"white" + }, + { + "name":"Text", + "fontId":"Verdana", + "fontSize":12, + "color":"white" + }, + { + "name":"Keyboard", + "fontId":"Verdana", + "fontSize":20, + "color":"black" + }, + { + "name":"Input", + "fontId":"Verdana", + "fontSize":24, + "color":"white" + } +], +"elements":[ + { + "info":"a prefab label class", + "id":"labelSmall", + "class":"label", + "size":[125,30], + "font":"Text" + } +] +} \ No newline at end of file diff --git a/exampleJson/bin/data/textures/cat1.jpg b/exampleJson/bin/data/textures/cat1.jpg new file mode 100644 index 0000000..1302d97 Binary files /dev/null and b/exampleJson/bin/data/textures/cat1.jpg differ diff --git a/exampleJson/src/main.cpp b/exampleJson/src/main.cpp new file mode 100644 index 0000000..56e5041 --- /dev/null +++ b/exampleJson/src/main.cpp @@ -0,0 +1,13 @@ +#include "ofMain.h" +#include "ofApp.h" + +//======================================================================== +int main( ){ + ofSetupOpenGL(1280,768,OF_WINDOW); // <-------- setup the GL context + + // this kicks off the running of my app + // can be OF_WINDOW or OF_FULLSCREEN + // pass in width and height too: + ofRunApp(new ofApp()); + +} diff --git a/exampleJson/src/ofApp.cpp b/exampleJson/src/ofApp.cpp new file mode 100644 index 0000000..3ba73d5 --- /dev/null +++ b/exampleJson/src/ofApp.cpp @@ -0,0 +1,109 @@ +#include "ofApp.h" + +using namespace ofxInterface; + +//-------------------------------------------------------------- +void ofApp::setup(){ + + // create asset manager and load assets from "assets.json" + assets = make_shared(ofxAssetManager()); + assets->setup(); + + // create a base Node + scene = new Node(); + scene->setSize(ofGetWidth(), ofGetHeight()); + ofxInterface::TouchManager::one().setup(scene); + + // create the scene from scene definition + GuiFactory factory; + factory.setup(ofLoadJson("guiElements.json"), assets); + factory.createElements(scene, ofLoadJson("gui.json")); + + // register key events from soft keyboard to input + // use static_cast to access special element functions + static_cast(scene->getChildWithName("input"))->registerKeyInput(static_cast(scene->getChildWithName("keyboard"))->keyPressed); + + // you can use lambda functions to create a callback for each node + // touchUp, touchDown, touchMoved and clicked are callbacks for every node + // we add a callback for the button changing the button description + scene->getChildWithName("button")->setTouchDownFunction([this](ofxInterface::TouchEvent& t) { + auto label = static_cast(scene->getChildWithName("button")->getChildWithName("description")); + label->setText("clicked"); + }); + scene->getChildWithName("button")->setTouchUpFunction([this](ofxInterface::TouchEvent& t) { + auto label = static_cast(scene->getChildWithName("button")->getChildWithName("description")); + label->setText("a button"); + }); +} + +//-------------------------------------------------------------- +void ofApp::update(){ + scene->update(ofGetLastFrameTime()); +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + scene->render(); + + if (isDebug) { + scene->renderDebug(); + } + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + if (key == 'd') { + isDebug = !isDebug; + } +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + ofxInterface::TouchManager::one().touchMove(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + ofxInterface::TouchManager::one().touchDown(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + ofxInterface::TouchManager::one().touchUp(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/exampleJson/src/ofApp.h b/exampleJson/src/ofApp.h new file mode 100644 index 0000000..4cf1285 --- /dev/null +++ b/exampleJson/src/ofApp.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ofMain.h" +#include "ofxAssetManager.h" +#include "ofxInterface.h" + +class ofApp : public ofBaseApp{ + + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + + shared_ptr assets; + ofxInterface::Node* scene; + + bool isDebug = false; +}; diff --git a/exampleJsonLiveEdit/addons.make b/exampleJsonLiveEdit/addons.make new file mode 100644 index 0000000..333745d --- /dev/null +++ b/exampleJsonLiveEdit/addons.make @@ -0,0 +1,7 @@ +ofxAnimatable +ofxAssetManager +ofxEasing +ofxFontStash2 +ofxInterface +ofxIO +ofxPoco diff --git a/exampleJsonLiveEdit/bin/data/.gitkeep b/exampleJsonLiveEdit/bin/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/exampleJsonLiveEdit/bin/data/assets.json b/exampleJsonLiveEdit/bin/data/assets.json new file mode 100644 index 0000000..3c9216d --- /dev/null +++ b/exampleJsonLiveEdit/bin/data/assets.json @@ -0,0 +1,20 @@ +{ + "textures":{ + "folder":"textures", + "info":"files [[id,path]]", + "files":[ + ["cat1","cat1.jpg"], + ["cat2","cat2.jpg"], + ["cat3","cat3.jpg"] + ] + }, + "fonts":{ + "folder":"fonts", + "info":"files [[id,path]]", + "files":[ + ["Verdana","verdana.ttf"], + ["Franklin Gothic","frabk.ttf"] + ] + } + +} \ No newline at end of file diff --git a/exampleJsonLiveEdit/bin/data/fonts/frabk.ttf b/exampleJsonLiveEdit/bin/data/fonts/frabk.ttf new file mode 100644 index 0000000..21c4ecf Binary files /dev/null and b/exampleJsonLiveEdit/bin/data/fonts/frabk.ttf differ diff --git a/exampleJsonLiveEdit/bin/data/fonts/verdana.ttf b/exampleJsonLiveEdit/bin/data/fonts/verdana.ttf new file mode 100644 index 0000000..8f25a64 Binary files /dev/null and b/exampleJsonLiveEdit/bin/data/fonts/verdana.ttf differ diff --git a/exampleJsonLiveEdit/bin/data/gui.json b/exampleJsonLiveEdit/bin/data/gui.json new file mode 100644 index 0000000..46745df --- /dev/null +++ b/exampleJsonLiveEdit/bin/data/gui.json @@ -0,0 +1,105 @@ +{ + "elements":[ + { + "id":"background", + "class":"colorPanel", + "color":"bg", + "size":[1280,768] + },{ + "id":"Head", + "class":"label", + "font":"Headline", + "position":[80,44], + "size":[1280,40], + "text":"Interactive GUI edit" + },{ + "id":"text", + "class":"textField", + "size":[820,40], + "position":[80,100], + "text":[ + "" + ] + },{ + "id":"button", + "class":"modalElement", + "position":[80,160], + "size":[32,32], + "colorActive":"black", + "colorSelected":"white", + "colorInactive":"black", + "info": "sub elements can be defined", + "elements":[ + { + "id":"description", + "class":"labelSmall", + "position":[40,10], + "text": "a button" + } + ] + },{ + "id":"checker", + "class":"simpleChecker", + "position":[250,160], + "size":[32,32], + "colorActive":"black", + "colorSelected":"white", + "colorInactive":"black", + "elements":[ + { + "id":"description", + "class":"labelSmall", + "position":[40,10], + "text": "a simpleChecker" + } + ] + },{ + "id":"border", + "class":"colorPanel", + "color":"white", + "position":[0,210], + "size":[1280,2] + },{ + "id":"container", + "class":"scrollableContainer", + "position":[0,220], + "size":[1280,548], + "sizeScrollableArea":[1280,1600], + "bgColor":"bg", + "elements":[ + { + "id":"description2", + "class":"labelSmall", + "position":[40,10], + "size":[400,40], + "text": "a scrollable container" + }, + { + "id":"pic1", + "class":"texture", + "position":[40,30], + "texture":"cat1", + "size":[500,500] + + }, + { + "id":"pic2", + "class":"texture", + "position":[40,560], + "texture":"cat2", + "size":[500,500] + + }, + { + "id":"pic3", + "class":"texture", + "position":[40,1090], + "texture":"cat3", + "size":[500,500], + "tint":"tintRed" + } + ] + } + ] +} \ No newline at end of file diff --git a/exampleJsonLiveEdit/bin/data/guiElements.json b/exampleJsonLiveEdit/bin/data/guiElements.json new file mode 100644 index 0000000..2263d9d --- /dev/null +++ b/exampleJsonLiveEdit/bin/data/guiElements.json @@ -0,0 +1,34 @@ +{ + "colors":[ + {"id":"black","color":[0,0,0]}, + {"id":"white","color":[255,255,255]}, + {"id":"bg","color":[30,30,30]}, + {"id":"tintRed","color":[250,0,0,120]}, + {"id":"inactiveSlider","color":[31,31,31]}, + {"id":"dropShadow","color":[0,0,0,50]}, + {"id":"iconBg","color":[31,31,31]} + ], + "fontStyles":[ + { + "name":"Headline", + "fontId":"Franklin Gothic", + "fontSize":40, + "color":"white" + }, + { + "name":"Text", + "fontId":"Verdana", + "fontSize":12, + "color":"white" + } +], +"elements":[ + { + "info":"a prefab label class", + "id":"labelSmall", + "class":"label", + "size":[125,30], + "font":"Text" + } +] +} \ No newline at end of file diff --git a/exampleJsonLiveEdit/bin/data/textures/cat1.jpg b/exampleJsonLiveEdit/bin/data/textures/cat1.jpg new file mode 100644 index 0000000..1302d97 Binary files /dev/null and b/exampleJsonLiveEdit/bin/data/textures/cat1.jpg differ diff --git a/exampleJsonLiveEdit/bin/data/textures/cat2.jpg b/exampleJsonLiveEdit/bin/data/textures/cat2.jpg new file mode 100644 index 0000000..f548a73 Binary files /dev/null and b/exampleJsonLiveEdit/bin/data/textures/cat2.jpg differ diff --git a/exampleJsonLiveEdit/bin/data/textures/cat3.jpg b/exampleJsonLiveEdit/bin/data/textures/cat3.jpg new file mode 100644 index 0000000..49fcdb1 Binary files /dev/null and b/exampleJsonLiveEdit/bin/data/textures/cat3.jpg differ diff --git a/exampleJsonLiveEdit/src/main.cpp b/exampleJsonLiveEdit/src/main.cpp new file mode 100644 index 0000000..56e5041 --- /dev/null +++ b/exampleJsonLiveEdit/src/main.cpp @@ -0,0 +1,13 @@ +#include "ofMain.h" +#include "ofApp.h" + +//======================================================================== +int main( ){ + ofSetupOpenGL(1280,768,OF_WINDOW); // <-------- setup the GL context + + // this kicks off the running of my app + // can be OF_WINDOW or OF_FULLSCREEN + // pass in width and height too: + ofRunApp(new ofApp()); + +} diff --git a/exampleJsonLiveEdit/src/ofApp.cpp b/exampleJsonLiveEdit/src/ofApp.cpp new file mode 100644 index 0000000..21db340 --- /dev/null +++ b/exampleJsonLiveEdit/src/ofApp.cpp @@ -0,0 +1,111 @@ +#include "ofApp.h" + +using namespace ofxInterface; + +//-------------------------------------------------------------- +void ofApp::setup(){ + + // create asset manager and load assets from "assets.json" + assets = make_shared(ofxAssetManager()); + assets->setup(); + + // create a base Node + scene = new Node(); + scene->setSize(ofGetWidth(), ofGetHeight()); + ofxInterface::TouchManager::one().setup(scene); + + // create the scene from scene definition + GuiFactory factory; + factory.setup(ofLoadJson("guiElements.json"), assets); + factory.createElements(scene, ofLoadJson("gui.json")); + + // add the file watcher to recreate scene when file changed + ofAddListener(watcher.events.onItemModified, this, &ofApp::onGuiJsonChanged); + watcher.addPath(ofToDataPath("", true), true, &fileFilter); +} + +//-------------------------------------------------------------- +void ofApp::update(){ + scene->update(ofGetLastFrameTime()); +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + scene->render(); + + if (isDebug) { + scene->renderDebug(); + } + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + if (key == 'd') { + isDebug = !isDebug; + } +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + ofxInterface::TouchManager::one().touchMove(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + ofxInterface::TouchManager::one().touchDown(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + ofxInterface::TouchManager::one().touchUp(0, ofVec2f(x, y)); +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +void ofApp::onGuiJsonChanged(const ofxIO::DirectoryWatcherManager::DirectoryEvent & evt) +{ + // delete all children from base node + while (scene->getChildren().size() >0) + { + scene->removeChild(0); + } + + // create new elements from definition + GuiFactory factory; + factory.setup(ofLoadJson("guiElements.json"), assets); + factory.createElements(scene, ofLoadJson("gui.json")); +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/exampleJsonLiveEdit/src/ofApp.h b/exampleJsonLiveEdit/src/ofApp.h new file mode 100644 index 0000000..3ff9e99 --- /dev/null +++ b/exampleJsonLiveEdit/src/ofApp.h @@ -0,0 +1,36 @@ +#pragma once + +#include "ofMain.h" +#include "ofxAssetManager.h" +#include "ofxInterface.h" +#include "ofxIO.h" + +class ofApp : public ofBaseApp{ + + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + + void onGuiJsonChanged(const ofxIO::DirectoryWatcherManager::DirectoryEvent& evt); + + shared_ptr assets; + ofxInterface::Node* scene; + + bool isDebug = false; + + ofxIO::DirectoryWatcherManager watcher; + ofxIO::HiddenFileFilter fileFilter; +}; diff --git a/src/AnimatableNode.cpp b/src/AnimatableNode.cpp index dad683c..721d4c0 100644 --- a/src/AnimatableNode.cpp +++ b/src/AnimatableNode.cpp @@ -66,8 +66,7 @@ void AnimatableNode::update(float dt) setPosition(nodePositionAnimation.source.getInterpolated(nodePositionAnimation.target, prog)); if (nodePositionAnimation.progress >= nodePositionAnimation.delay+nodePositionAnimation.duration) { nodePositionAnimation.bActive = false; - function f = nodePositionAnimation.onEnd; - f(); + nodePositionAnimation.onEnd(); } } @@ -80,8 +79,7 @@ void AnimatableNode::update(float dt) *anim.second.color = anim.second.source.getLerped(anim.second.target, prog); if (anim.second.progress >= anim.second.delay+anim.second.duration) { rmcolor.push_back(anim.first); - function f = anim.second.onEnd; - f(); + anim.second.onEnd(); } } // go over again and remove completed animations @@ -100,8 +98,7 @@ void AnimatableNode::update(float dt) *anim.second.value = ofMap(prog, 0, 1, anim.second.source, anim.second.target, true); if (anim.second.progress >= anim.second.delay+anim.second.duration) { rmfloat.push_back(anim.first); - function f = anim.second.onEnd; - f(); + anim.second.onEnd(); } } // go over again and remove completed animations diff --git a/src/GuiFactory.cpp b/src/GuiFactory.cpp new file mode 100644 index 0000000..61d2631 --- /dev/null +++ b/src/GuiFactory.cpp @@ -0,0 +1,769 @@ +#include "GuiFactory.h" + +namespace ofxInterface +{ + GuiFactory::GuiFactory() { + } + + + GuiFactory::~GuiFactory() { + } + + void GuiFactory::setup(ofJson config, shared_ptr assets_) { + assets = assets_; + + // init colors + if (!config["colors"].is_null()) { + for (auto& color : config["colors"]) { + colors.insert(pair(color["id"].get(), colorFromJson(color["color"],colors))); + + + } + }else { + ofLogError("GuiFactory::setup", "no font colors defined, gui elements will use predefined ones"); + } + + // init font styles + + // disable logging each font that is loaded + auto currentLogLevel = ofGetLogLevel(); + ofSetLogLevel(OF_LOG_ERROR); + + float fontScale = 1.5; + if (!config["fontScale"].is_null()) { + fontScale = config["fontScale"].get(); + } + if (!config["fontStyles"].is_null()) { + for (auto& font : config["fontStyles"]) { + ofxFontStash2::Style style; + style.fontID = font["fontId"].get(); + style.fontSize = font["fontSize"].get()*fontScale; + style.color = colors[font["color"].get()]; + if (!font["lineHeight"].is_null()) style.lineHeightMult = font["lineHeight"].get(); + if (!font["spacing"].is_null()) style.spacing = font["spacing"].get(); + + assets->getFonts()->addStyle(font["name"].get(), style); + } + } else { + ofLogError("GuiFactory::setup", "no font styles defined, gui elements using fonts won't be available" ); + } + ofSetLogLevel(currentLogLevel); + + // init elements + if (!config["elements"].is_null()) { + styles = config["elements"]; + }else { + ofLogVerbose("GuiFactory::setup", "no elements defined"); + } + + + // register creation functions + registerCreationFunction("node", [this](ofJson config, ofJson style) {return this->getNode(config, style); }); + registerCreationFunction("canvas", [this](ofJson config, ofJson style) {return this->getNode(config, style); }); + registerCreationFunction("label", [this](ofJson config, ofJson style) {return this->getLabel(config, style); }); + registerCreationFunction("textField", [this](ofJson config, ofJson style) {return this->getTextField(config, style); }); + registerCreationFunction("colorPanel", [this](ofJson config, ofJson style) {return this->getColorPanel(config, style); }); + registerCreationFunction("scrollableContainer", [this](ofJson config, ofJson style) {return this->getScrollableContainer(config, style); }); + registerCreationFunction("texture", [this](ofJson config, ofJson style) {return this->getTextureNode(config, style); }); + registerCreationFunction("modalElement", [this](ofJson config, ofJson style) {return this->getModal(config, style); }); + registerCreationFunction("simpleChecker", [this](ofJson config, ofJson style) {return this->getSimpleChecker(config, style); }); + registerCreationFunction("softKeyboard", [this](ofJson config, ofJson style) {return this->getSoftKeyboard(config, style); }); + registerCreationFunction("textInput", [this](ofJson config, ofJson style) {return this->getTextInput(config, style); }); + registerCreationFunction("slider", [this](ofJson config, ofJson style) {return this->getSlider(config, style); }); + } + + ///\brief create all GUI elements from json and add them to parent node + void GuiFactory::createElements(Node * parent, ofJson config) { + for (auto& e : config["elements"]) { + auto elem = getElement(e); + if (elem != nullptr) { + parent->addChild(elem); + // create child elements + if (!e["elements"].is_null() && e["elements"].is_array()) { + createElements(elem, e); + } + } + } + } + + Node * GuiFactory::getElement(ofJson config) { + + // usinf "node" if no class specified + string className; + if (config["class"].is_null()) { + className = "node"; + } + else { + className = config["class"].get(); + } + ofJson styleConfig = getStyle(className); + + if (styleConfig["id"] != "#error") { + string elemClass = styleConfig["class"].get(); + ofLogVerbose() <<"create element : " << config["id"].get() << " ( " << elemClass << " -> " << styleConfig["id"].get() << " )"; + Node* ret = nullptr; + if (creationTable.find(elemClass) != creationTable.end()) { + ret = (this->creationTable[elemClass])(config, styleConfig); + } else { + ofLogError("GuiFactory", "Can't create element \"" + config["id"].get() + "\" : no definition for class \"" + elemClass + "\" available."); + return nullptr; + } + return ret; + } + return nullptr; + } + Node * GuiFactory::getNode(ofJson config, ofJson style) { + NodeSettings s; + readNodeSettings(s,config,style); + Node* n = new Node(); + n->setup(s); + return n; + } + // -------------------------------------------------------------------------------------------------------------- + Node * GuiFactory::getLabel(ofJson config, ofJson style) { + + LabelSettings s; + readNodeSettings(s, config, style); + + s.font = assets->getFonts(); + if (hasValue("font", config, style)) { + s.style = assets->getFonts()->getStyle(getValue("font", config, style)); + } + + if (hasValue("text", config, style)) { + s.text = getValue("text", config, style); + } + + if (hasValue("alignment", config, style)) { + if (getValue("alignment", config, style) == "center") { + s.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_CENTER; + } else if (getValue("alignment", config, style) == "right") { + s.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_RIGHT; + } + } + + if (hasValue("shadow", config, style)) { + s.isDropshadow = true; + if (hasValue("size", config["shadow"], style["shadow"])) { + s.shadowSize = getValue("size", config["shadow"], style["shadow"]); + } + if (hasValue("x", config["shadow"], style["shadow"])) { + s.shadowPos.x = getValue("x", config["shadow"], style["shadow"]); + } + if (hasValue("y", config["shadow"], style["shadow"])) { + s.shadowPos.y = getValue("y", config["shadow"], style["shadow"]); + } + if (hasValue("color", config["shadow"], style["shadow"])) { + + s.shadowColor = colors[getValue("color", config["shadow"], style["shadow"])]; + } + } + + Label* label = new Label(); + label->setup(s); + + return label; + } + + Node * GuiFactory::getTextField(ofJson config, ofJson style) { + TextFieldSettings s; + readNodeSettings(s, config, style); + + s.font = assets->getFonts(); + + if (!config["text"].is_null()) { + if (config["text"].is_array()) { + for (auto elem : config["text"]) { + s.text += elem.get(); + } + } else { + s.text = config["text"].get(); + } + } + if (hasValue("alignment", config, style)) { + string al = getValue("alignment", config, style); + if (al == "left") { + s.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_LEFT; + } else if (al == "right") { + s.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_RIGHT; + } else if (al == "center") { + s.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_CENTER; + } + } + + + TextField* textField = new TextField(); + textField->setup(s); + return textField; + } + + Node * GuiFactory::getColorPanel(ofJson config, ofJson style) { + ColorPanelSettings s; + readColorpanelSettings(s, config, style); + + ColorPanel* p = new ColorPanel(); + p->setup(s); + return p; + + } + + + + Node * GuiFactory::getScrollableContainer(ofJson config, ofJson style) { + ScrollableContainerSettings s; + readScrollableContainerSettings(s, config, style); + ScrollableContainer * p = new ScrollableContainer(); + p->setup(s); + return p; + } + + Node * GuiFactory::getTextureNode(ofJson config, ofJson style) { + TextureNodeSettings s; + readTextureSettings(s,config,style); + TextureNode* tex = new TextureNode(); + tex->setup(s); + return tex; + } + + Node * GuiFactory::getModal(ofJson config, ofJson style) { + ModalElementSettings s; + readModalElementSettings(s, config, style); + ModalElement* ret = new ModalElement(); + ret->setup(s); + return ret; + } + + Node * GuiFactory::getSimpleChecker(ofJson config, ofJson style) { + ModalElementSettings s; + readModalElementSettings(s, config, style); + + SimpleChecker* ret = new SimpleChecker(); + ret->setup(s); + return ret; + } + + Node * GuiFactory::getSoftKeyboard(ofJson config, ofJson style) + { + SoftKeyboardSettings settings; + readNodeSettings(settings, config, style); + + SoftKeyboard* ret = new SoftKeyboard(); + + settings.font = assets->getFonts(); + if (hasValue("font", config, style)) { + settings.style = assets->getFonts()->getStyle(getValue("font", config, style)); + } + if (hasValue("bgColor", config, style)) { + settings.bgColor = colors[getValue("bgColor", config, style)]; + } + if (hasValue("colorActive", config, style)) { + settings.colorActive = colors[getValue("colorActive", config, style)]; + } + if (hasValue("colorInactive", config, style)) { + settings.colorInactive = colors[getValue("colorInactive", config, style)]; + } + if (hasValue("colorSelected", config, style)) { + settings.colorSelected = colors[getValue("colorSelected", config, style)]; + } + + if (hasValue("borderRadius", config, style)) { + settings.borderRadius = getValue("borderRadius", config, style); + } + if (hasValue("borderWidth", config, style)) { + settings.borderWidth = getValue("borderWidth", config, style); + } + if (hasValue("margin", config, style)) { + settings.margin = getValue("margin", config, style); + } + if (hasValue("padding", config, style)) { + settings.padding = getValue("padding", config, style); + } + if (hasValue("layout", config, style)) { + settings.layout = getValue("layout", config, style); + } + if (hasValue("textureKeys", config, style)) { + ofJson mapping; + if (config["textureKeys"] != nullptr) { + mapping = config["textureKeys"]; + } + else if (style["textureKeys"] != nullptr) { + mapping = style["textureKeys"]; + } + + for (auto& entry:mapping.items()) + { + settings.textureKeys.insert(make_pair(ofToInt(entry.key()), assets->getTexture(entry.value().get()))); + } + } + + ret->setup(settings); + return ret; + } + + Node * GuiFactory::getTextInput(ofJson config, ofJson style) + { + TextInputSettings settings; + readTextInputSettings(settings, config, style); + + + TextInput* t = new TextInput(); + t->setup(settings); + return t; + } + + Node * GuiFactory::getSlider(ofJson config, ofJson style) + { + SliderSettings settings; + readNodeSettings(settings, config, style); + + if (hasValue("direction", config, style)) { + string val = ofToUpper(getValue("direction", config, style)); + if (val == "HORIZONTAL") { + settings.direction = HORIZONTAL; + } + else if (val == "VERTICAL") { + settings.direction = VERTICAL; + } + } + if (hasValue("colorActive", config, style)) { + settings.colorActive = colors[getValue("colorActive", config, style)]; + } + if (hasValue("colorInactive", config, style)) { + settings.colorInactive = colors[getValue("colorInactive", config, style)]; + } + if (hasValue("colorSelected", config, style)) { + settings.colorSelected = colors[getValue("colorSelected", config, style)]; + } + + if (hasValue("lineWidth", config, style)) { + settings.lineWidth = getValue("lineWidth", config, style); + } + + Slider* ret = new Slider(); + ret->setup(settings); + return ret; + } + + ofJson GuiFactory::getTemplate(string templateId) + { + for (auto& elem:styles){ + if (elem["id"].get() == templateId) { + return elem; + } + } + return ofJson{ {"error","style not found"} }; + } + + ofJson GuiFactory::getStyle(string id) { + for (auto& s : styles) { + if (s["id"] == id) { + return s; + } + } + if (id != "canvas") { + ofLogError("GuiFactory::getStyle", "style <" + id + "> not found"); + } + + return ofJson({ {"id","#error"} }); + } + void GuiFactory::readNodeSettings(NodeSettings & settings, ofJson config, ofJson style) { + readEffectSettings(settings, config, style); + settings.name = config["id"].get(); + if (hasValue("position", config, style)) { + settings.position = vec2fFromJson(getValue("position", config, style)); + } + if (hasValue("size", config, style)) { + settings.size = vec2fFromJson(getValue("size", config, style)); + } + if (hasValue("plane", config, style)) { + settings.plane = getValue("plane", config, style); + } + if (hasValue("renderClip", config, style)) { + settings.renderClip = getValue("plane", config, style); + } + if (hasValue("active", config, style)) { + settings.isActive = getValue("active", config, style); + } + } + + void GuiFactory::readModalElementSettings(ModalElementSettings & settings, ofJson config, ofJson style) { + readNodeSettings(settings, config, style); + if (hasValue("type", config, style)) { + string val = getValue("type", config, style); + if (val == "button") { + settings.type = BUTTON; + }else if (val == "checker") { + settings.type = CHECKER; + } else if (val == "radio") { + settings.type = RADIO; + } + } + if (hasValue("colorActive", config, style)) { + settings.colorActive = colors[getValue("colorActive", config, style)]; + } + if (hasValue("colorInactive", config, style)) { + settings.colorInactive = colors[getValue("colorInactive", config, style)]; + } + if (hasValue("colorSelected", config, style)) { + settings.colorSelected = colors[getValue("colorSelected", config, style)]; + } + } + + void GuiFactory::readScrollableContainerSettings(ScrollableContainerSettings & settings, ofJson config, ofJson style) { + readNodeSettings(settings, config, style); + if (hasValue("sizeScrollableArea", config, style)) { + settings.sizeScrollableArea = vec2fFromJson(getValue("sizeScrollableArea", config, style)); + } else { + settings.sizeScrollableArea = settings.size; + } + if (hasValue("bgColor", config, style)) { + settings.bgColor = colors[getValue("bgColor", config, style)]; + } + if (hasValue("scrollActiveColor", config, style)) { + settings.scrollActiveColor = colors[getValue("scrollActiveColor", config, style)]; + } + if (hasValue("scrollInactiveColor", config, style)) { + settings.scrollInactiveColor = colors[getValue("scrollInactiveColor", config, style)]; + } + } + + void GuiFactory::readColorpanelSettings(ColorPanelSettings & settings, ofJson config, ofJson style) { + readNodeSettings(settings, config, style); + + if (hasValue("strokeWidth", config, style)) { + settings.strokeWidth = getValue("strokeWidth", config, style); + } + if (hasValue("drawBackground", config, style)) { + settings.bDrawBackground = getValue("drawBackground", config, style); + } + if (hasValue("borderRadius", config, style)) { + settings.borderRadius = getValue("borderRadius", config, style); + } + if (hasValue("color", config, style)) { + if (getValueType("color", config, style) == "array") { + auto cs = config["color"] == nullptr ? style["color"] : config["color"]; + settings.colors.clear(); + for (auto& c : cs) { + settings.colors.push_back(colors[c.get()]); + } + } + else { + settings.colors.front() = colors[getValue("color", config, style)]; + } + } + if (hasValue("strokeColor", config, style)) { + settings.strokeColor = colors[getValue("strokeColor", config, style)]; + } + if (hasValue("gradientDirection", config, style)) { + settings.gradientDirection = getValue("gradientDirection", config, style); + } + if (hasValue("gradientColorPositions", config, style)) { + auto gs = config["gradientColorPositions"] == nullptr ? style["gradientColorPositions"] : config["gradientColorPositions"]; + settings.gradientColorPositions.clear(); + for (auto& g : gs) { + settings.colors.push_back(g.get()); + } + } + + + vector gradientColorPositions; + } + + void GuiFactory::readTextureSettings(TextureNodeSettings & settings, ofJson config, ofJson style) + { + readNodeSettings(settings, config, style); + + if (hasValue("texture", config, style)) { + settings.texture = assets->getTexture(getValue("texture", config, style)); + if (!hasValue("size", config, style)) { + settings.size = ofVec2f(assets->getTexture(getValue("texture", config, style)).getWidth(), + assets->getTexture(getValue("texture", config, style)).getHeight()); + } + } + if (hasValue("tint", config, style)) { + settings.tinting = colors[getValue("tint", config, style)]; + } + if (hasValue("scaleMode", config, style)) { + string al = getValue("scaleMode", config, style); + if (al == "fill") { + settings.scaleMode = OF_SCALEMODE_FILL; + } + else if (al == "fit") { + settings.scaleMode = OF_SCALEMODE_FIT; + } + else if (al == "center") { + settings.scaleMode = OF_SCALEMODE_CENTER; + } + else if (al == "stretch") { + settings.scaleMode = OF_SCALEMODE_STRETCH_TO_FILL; + } + } + + if (hasValue("verticalAlign", config, style)) { + string al = getValue("verticalAlign", config, style); + if (al == "top") { + settings.verticalAlign = OF_ALIGN_VERT_TOP; + } + else if (al == "bottom") { + settings.verticalAlign = OF_ALIGN_VERT_BOTTOM; + } + else if (al == "center") { + settings.verticalAlign = OF_ALIGN_VERT_CENTER; + } + } + + if (hasValue("horizontalAlign", config, style)) { + string al = getValue("horizontalAlign", config, style); + if (al == "left") { + settings.horizontalAlign = OF_ALIGN_HORZ_LEFT; + } + else if (al == "right") { + settings.horizontalAlign = OF_ALIGN_HORZ_RIGHT; + } + else if (al == "center") { + settings.horizontalAlign = OF_ALIGN_HORZ_CENTER; + } + } + + if (hasValue("blendmode", config, style)) { + string al = getValue("blendmode", config, style); + if (al == "alpha") { + settings.blendmode = OF_BLENDMODE_ALPHA; + } + else if (al == "none") { + settings.blendmode = OF_BLENDMODE_DISABLED; + } + else if (al == "add") { + settings.blendmode = OF_BLENDMODE_ADD; + } + else if (al == "multiply") { + settings.blendmode = OF_BLENDMODE_MULTIPLY; + } + else if (al == "screen") { + settings.blendmode = OF_BLENDMODE_SCREEN; + } + else if (al == "subtract") { + settings.blendmode = OF_BLENDMODE_SUBTRACT; + } + } + } + + void GuiFactory::readTextInputSettings(TextInputSettings& settings, ofJson config, ofJson style) + { + readModalElementSettings(settings, config, style); + + settings.font = assets->getFonts(); + if (hasValue("font", config, style)) { + settings.style = assets->getFonts()->getStyle(getValue("font", config, style)); + } + + if (hasValue("maxChars", config, style)) { + settings.maxChars = getValue("maxChars", config, style); + } + if (hasValue("description", config, style)) { + settings.descriptionText = getValue("description", config, style); + } + if (hasValue("enableNewline", config, style)) { + settings.enableNewline = getValue("enableNewline", config, style); + } + + if (hasValue("alignment", config, style)) { + if (getValue("alignment", config, style) == "center") { + settings.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_CENTER; + } + else if (getValue("alignment", config, style) == "right") { + settings.horzAlignment = ofAlignHorz::OF_ALIGN_HORZ_RIGHT; + } + } + + if (hasValue("autoResize", config, style)) { + settings.autoResize = getValue("autoResize", config, style); + } + } + + void GuiFactory::readEffectSettings(NodeSettings& settings, ofJson config, ofJson style) + { + if (hasValue("nodeEffects", config, style)) { + if (hasValue("shadow", config["nodeEffects"], style["nodeEffects"])) { + if (hasValue("color", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"])) { + settings.effects.dropShadow.color = colors[getValue("color", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"])]; + } + if (hasValue("size", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"])) { + settings.effects.dropShadow.size = getValue("size", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"]); + } + if (hasValue("position", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"])) { + settings.effects.dropShadow.position = vec2fFromJson(getValue("position", config["nodeEffects"]["shadow"], style["nodeEffects"]["shadow"])); + } + + } + } + + } + + ofColor GuiFactory::colorFromJson(ofJson val, map colors) { + if (val.is_string()) { // read ofParam + if (ofIsStringInString(val.get(), ".")) { + ofParameter pTemp; + pTemp.fromString(val); + return pTemp.get(); + } + else { + ofParameter pTemp; + pTemp.fromString(val); + return pTemp.get(); + } + } + else if (val.is_array()) { // read as rgba array + switch (val.size()) { + case 1: + return ofColor(val[0].get()); + break; + case 2: + return ofColor(val[0], val[0], val[0], val[1]); + break; + case 3: + return ofColor(val[0], val[1], val[2]); + break; + case 4: + return ofColor(val[0], val[1], val[2], val[3]); + break; + default: + return ofColor(); + break; + } + } + else if(val.is_object()) + { + ofColor base; + if (val["base"].is_null()) { + ofLogError("no base Color defined"); + } + else { + if (colors.find(val["base"].get()) != colors.end()) { + base = colors[val["base"].get()]; + } + if (!val["r"].is_null()) { + base.r = val["r"].get(); + } + if (!val["g"].is_null()) { + base.g = val["g"].get(); + } + if (!val["b"].is_null()) { + base.b = val["b"].get(); + } + if (!val["a"].is_null()) { + base.a = val["a"].get(); + } + + } + + return base; + } + return ofColor(); + } + + ofVec2f GuiFactory::vec2fFromJson(ofJson val) { + switch (val.size()) { + case 1: + // interprete string + if (val.is_string()) { + ofParameter pTemp; + pTemp.fromString(val); + return pTemp.get(); + }// interprete array + else + return ofVec2f(val[0].get()); + + break; + case 2: + return ofVec2f(val[0], val[1]); + break; + default: + return ofVec2f(); + break; + } + } + + ofVec3f GuiFactory::vec3fFromJson(ofJson val) { + switch (val.size()) { + case 1: + // interprete string + if (val.is_string()) { + ofParameter pTemp; + pTemp.fromString(val); + return pTemp.get(); + }// interprete array + else + return ofVec3f(val[0].get()); + + break; + case 2: + return ofVec3f(val[0], val[1]); + break; + case 3: + return ofVec3f(val[0], val[1], val[2]); + break; + default: + return ofVec2f(); + break; + } + } + + void GuiFactory::addStyle(ofJson style, ofJson& styleConfig, bool overwrite) + { + vector styleList; + bool hasStyle = false; + for (auto& elem : styleConfig) { + if (elem["id"] == style["id"]) { + hasStyle = true; + if (overwrite) { + styleList.push_back(style); + } else { + styleList.push_back(elem); + } + } else { + styleList.push_back(elem); + } + } + if (!hasStyle) { + styleList.push_back(style); + } + styleConfig = styleList; + } + + bool GuiFactory::hasValue(string valueName, ofJson config, ofJson style) { + if (!config[valueName].is_null()) { + return true; + } + if (!style[valueName].is_null()) { + return true; + } + return false; + } + string GuiFactory::getValueType(string valueName, ofJson config, ofJson style) + { + string ret = "null"; + if (config[valueName] != nullptr) { + ret = config[valueName].type_name(); + } + else if (style[valueName] != nullptr) { + ret = style[valueName].type_name(); + } + return ret; + } + ///\brief register a creation function + /// + /// since it is not self-explanatory, here you have an example (using a lamda function) + /// registerCreationFunction("a functionname", [this](ofJson config, ofJson style) {return this->myFunction(config, style); }) + /// + /// if someone finds a way to achive this easier, please edit ;) + void GuiFactory::registerCreationFunction(string name, creationFunction fct) { + // register function in table + creationTable[name] = fct; + + // create base style for factory + addStyle(ofJson{ {"id",name },{"class",name} }, styles); + } + + +} diff --git a/src/GuiFactory.h b/src/GuiFactory.h new file mode 100644 index 0000000..5f9f2be --- /dev/null +++ b/src/GuiFactory.h @@ -0,0 +1,108 @@ +#pragma once +#include "ofMain.h" +#include "ofxAssetManager.h" + +#include "Label.h" +#include "TextField.h" +#include "ColorPanel.h" +#include "ScrollableContainer.h" +#include "TextureNode.h" +#include "SimpleChecker.h" +#include "SoftKeyboard.h" +#include "TextInput.h" +#include "Slider.h" + + +///\brief defines a function for creating gui elements +/// +/// since it looks a little overcomplicated (maybe it is) i try to explain it +/// the GuiFactory should be subclassed and therefore you need to register creation functions +/// for your own gui elements. Every function is set in the form of: +/// ofxInterface::Node*(ofJson, ofJson) +/// to register function from subclasses you need to use a std::function template +/// +/// typedef does not work with std::function thats why we used a struct +/// https://stackoverflow.com/questions/27002263/c11-typedef-stdfunction-and-argument-on-itself +struct creationFunction : std::function < ofxInterface::Node*(ofJson, ofJson)> { + using std::function < ofxInterface::Node*(ofJson, ofJson)>::function; +}; + +namespace ofxInterface +{ + class GuiFactory + { + public: + GuiFactory(); + ~GuiFactory(); + + virtual void setup(ofJson config, shared_ptr assets); + + void createElements(Node* parent, ofJson config); + Node* getElement(ofJson config); + + Node* getNode(ofJson config, ofJson style); + Node* getLabel(ofJson config, ofJson style); + Node* getTextField(ofJson config, ofJson style); + Node* getColorPanel(ofJson config, ofJson style); + Node* getScrollableContainer(ofJson config, ofJson style); + Node* getTextureNode(ofJson config, ofJson style); + Node* getModal(ofJson config, ofJson style); + Node* getSimpleChecker(ofJson config, ofJson style); + Node* getSoftKeyboard(ofJson config, ofJson style); + Node* getTextInput(ofJson config, ofJson style); + Node* getSlider(ofJson config, ofJson style); + + ofJson getTemplate(string templateId); + + // -- Helper functions -- // + ofJson getStyle(string id); + + bool hasValue(string valueName, ofJson config, ofJson style); + static ofColor colorFromJson(ofJson val, map colors = map()); + static ofVec2f vec2fFromJson(ofJson val); + static ofVec3f vec3fFromJson(ofJson val); + + void addStyle(ofJson style, ofJson& styleConfig, bool overwrite = false); + + template + ValueType getValue(string valueName, ofJson config, ofJson style); + string getValueType(string valueName, ofJson config, ofJson style); + + // -- function registration -- // + void registerCreationFunction(string name, creationFunction fct); + + // -- class functions --// + void readNodeSettings(NodeSettings& settings, ofJson config, ofJson style); + void readModalElementSettings(ModalElementSettings& settings, ofJson config, ofJson style); + void readScrollableContainerSettings(ScrollableContainerSettings& settings, ofJson config, ofJson style); + void readColorpanelSettings(ColorPanelSettings& settings, ofJson config, ofJson style); + void readTextureSettings(TextureNodeSettings& settings, ofJson config, ofJson style); + void readTextInputSettings(TextInputSettings& settings, ofJson config, ofJson style); + + void readEffectSettings(NodeSettings& settings, ofJson config, ofJson style); + + // -- variables -- // + shared_ptr assets; + map colors; + bool isDebug = false; + + + + private: + + ofJson styles; + std::map creationTable; + + }; + + template + inline ValueType GuiFactory::getValue(string valueName, ofJson config, ofJson style) { + ValueType ret; + if (config[valueName] != nullptr) { + ret = config[valueName].get(); + }else if (style[valueName] != nullptr) { + ret = style[valueName].get(); + } + return ret; + } +} diff --git a/src/Node.cpp b/src/Node.cpp index 1b48880..ad32414 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -20,11 +20,10 @@ ofColor Node::touchEnterNodeColor = ofColor(255, 0, 128); static int numInterfaceNodes = 0; - Node::~Node() { numInterfaceNodes--; - if (parent != NULL) { + if (parent != nullptr) { ((Node*)parent)->removeChild(this); } @@ -48,13 +47,15 @@ Node::Node() bClipTouch = false; bClipRender = false; bReceivingTouch = true; - data = NULL; + data = nullptr; bSendDestroy = true; bNodeAllowOneTouch = false; bNodeUpdateWhenHidden = false; bNodeTouched = false; nodeCurrentTouchId = -1; // not relevant + data = shared_ptr(new ofJson()); + dataIsJson = true; #ifdef OLDSORT sameDepthOffset = ofRandom(0, 1); #endif @@ -64,8 +65,13 @@ Node::Node() #endif #ifdef USE_OFX_HISTORY_PLOT - historyPlot = NULL; + historyPlot = nullptr; #endif + + ofAddListener(eventTouchDown, this, &Node::onTouchDown); + ofAddListener(eventTouchMove, this, &Node::onTouchMove); + ofAddListener(eventTouchUp, this, &Node::onTouchUp); + ofAddListener(eventClick, this, &Node::onClick); } Node::Node(const Node& mom) : ofNode(mom) @@ -84,6 +90,8 @@ Node::Node() bNodeUpdateWhenHidden = mom.bNodeUpdateWhenHidden; bNodeTouched = mom.bNodeTouched; nodeCurrentTouchId = -1; + data = mom.data; + dataIsJson = mom.dataIsJson; #ifdef OLDSORT sameDepthOffset = ofRandom(0, 1); #endif @@ -93,21 +101,48 @@ Node::Node() #endif #ifdef USE_OFX_HISTORY_PLOT - historyPlot = NULL; + historyPlot = nullptr; #endif // copy children for (auto& c: mom.getChildren()) { addChild(c->clone()); } - + ofAddListener(eventTouchDown, this, &Node::onTouchDown); + ofAddListener(eventTouchMove, this, &Node::onTouchMove); + ofAddListener(eventTouchUp, this, &Node::onTouchUp); } Node* Node::clone() { - return new Node(*this); -} + auto ret = new Node(*this); + /*for (auto& child : getChildren()) + { + ret->addChild(child->clone()); + }*/ + return ret; +} + +void Node::setup(NodeSettings settings) { + setName(settings.name); + setPosition(settings.position); + setSize(settings.size); + setPlane(settings.plane); + setRenderClip(settings.renderClip); + if (settings.isActive) { + activate(); + } + else { + deactivate(); + } + + if (settings.effects.dropShadow.size > 0) { + effectDropShadow.setup(settings.effects.dropShadow); + } + effectSettings = settings.effects; +} + int Node::getNumNodesAlive(){ return numInterfaceNodes; } @@ -119,18 +154,41 @@ Node* Node::getChildWithName(const std::string &searchName, int searchDepth) con } if (searchDepth==0) { - return NULL; + return nullptr; } for (int i=0; igetChildWithName(searchName, searchDepth-1); - if (node != NULL) { + if (node != nullptr) { return node; } } - return NULL; + return nullptr; +} + +vector Node::getChildrenWithName(const std::string& searchName, int searchDepth) const +{ + vector ret; + + if (searchName == name) { + ret.push_back((Node*)this); + } + + if (searchDepth == 0) { + return ret; + } + + for (int i = 0; i < childNodes.size(); i++) + { + vector retC = childNodes[i]->getChildrenWithName(searchName, searchDepth - 1); + if (retC.size() > 0) { + ret.insert(ret.end(), retC.begin(), retC.end()); + } + } + + return ret; } Node* Node::getParentWithName(const std::string &searchName, int searchDepth) const @@ -140,11 +198,11 @@ Node* Node::getParentWithName(const std::string &searchName, int searchDepth) co } if (searchDepth==0) { - return NULL; + return nullptr; } - if (parent == NULL) { - return NULL; + if (parent == nullptr) { + return nullptr; } return ((Node*)parent)->getParentWithName(searchName, searchDepth-1); @@ -167,7 +225,7 @@ void Node::drawDebug() ofDrawBitmapString(ss.str(), 0, -3); #ifdef USE_OFX_HISTORY_PLOT - if (historyPlot != NULL) { + if (historyPlot != nullptr) { ofFill(); historyPlot->draw(0, getHeight(), 100, 50); } @@ -181,6 +239,9 @@ void Node::drawBounds() void Node::render(bool forceAll) { + //not yet finally tested + renderDynamic(forceAll); + /* std::list sortedNodes; std::list::iterator it; @@ -216,6 +277,7 @@ void Node::render(bool forceAll) ofPopMatrix(); ofPopStyle(); } + */ } void Node::renderDebug(bool forceAll) @@ -250,6 +312,81 @@ void Node::renderDebug(bool forceAll) } } +void Node::renderDynamic(bool forceAll) +{ + std::list sortedNodes; + std::list::iterator it; + + if (forceAll) { + // get all nodes (visible and invisible) + getSubTreeList(sortedNodes); + } + else { + // get only visible nodes + getVisibleSubTreeList(sortedNodes); + } + + // sort scene by z (+z goes outside of the screen), plane is z + sortedNodes.sort(Node::bottomPlaneFirst); + + for (it = sortedNodes.begin(); it != sortedNodes.end(); it++) + { + ofPushStyle(); + ofPushMatrix(); + ofMultMatrix((*it)->getGlobalTransformMatrix()); + // use anchor + + if ((*it)->getGlobalRenderClip()) { + (*it)->enableScissor((*it)->getRenderClipRect()); + } + if ((*it)->getRenderChildrenInGroup()) { + (*it)->renderGroups(); + } + else { + + (*it)->drawDropShadow(); + (*it)->draw(); + } + + if ((*it)->getGlobalRenderClip()) { + (*it)->disableScissor(); + } + + ofPopMatrix(); + ofPopStyle(); + } +} + +void Node::renderGroups(bool forceAll) +{ + for (auto& child : childNodes) { + if (forceAll || child->getVisible()) { + + //if (child->getName() != "") cout << child->getName() << " predraw " <preDraw(); + //ofPushStyle(); + ofPushMatrix(); + ofMultMatrix(child->getGlobalTransformMatrix()); + // use anchor + + if (child->getGlobalRenderClip()) { + child->enableScissor(child->getRenderClipRect()); + } + //if (child->getName() != "") cout << child->getName() << " draw " << endl; + child->draw(); + if (child->getGlobalRenderClip()) { + child->disableScissor(); + } + + ofPopMatrix(); + //ofPopStyle(); + child->renderGroups(); + //if (child->getName() != "") cout << child->getName() << " postDraw" << endl; + child->postDraw(); + } + } +} + void Node::updateSubtree(float dt, bool forceAll) { update(dt); @@ -290,7 +427,7 @@ void Node::updateSubtreePostOrder(float dt, bool forceAll) bool Node::getGlobalRenderClip() { - if (getParent() != NULL) { + if (getParent() != nullptr) { return bClipRender || ((Node*)getParent())->getGlobalRenderClip(); } else { @@ -301,12 +438,13 @@ bool Node::getGlobalRenderClip() ofRectangle Node::getRenderClipRect() { ofRectangle parentRect; - if (getParent() != NULL) { + if (getParent() != nullptr) { parentRect = ((Node*)getParent())->getRenderClipRect(); } ofRectangle rect; if (bClipRender) { ofVec2f pos = getGlobalPosition(); + rect = ofRectangle(pos.x, pos.y, getGlobalWidth(), getGlobalHeight()); } @@ -333,7 +471,11 @@ void Node::enableScissor(float x, float y, float w, float h) ofVec2f local = toLocal(ofVec2f(x, y)); ofVec2f localScale = ofVec2f(w, h)*getGlobalScale(); glEnable(GL_SCISSOR_TEST); - glScissor(local.x, local.y, localScale.x, localScale.y); + //glScissor(local.x, local.y, localScale.x, localScale.y); + // openGL uses an inverted y - axis, so we need to invert the y value + float dy = ofGetWindowHeight()-h -y; + + glScissor(x, dy, localScale.x, localScale.y); } void Node::disableScissor() @@ -343,7 +485,7 @@ void Node::disableScissor() void Node::setCenteredH() { - if (parent == NULL) { + if (parent == nullptr) { return; } @@ -352,7 +494,7 @@ void Node::setCenteredH() void Node::setCenteredV() { - if (parent == NULL) { + if (parent == nullptr) { return; } @@ -361,7 +503,7 @@ void Node::setCenteredV() void Node::setCentered() { - if (parent == NULL) { + if (parent == nullptr) { return; } @@ -369,6 +511,62 @@ void Node::setCentered() (((Node*)parent)->getLocalHeight() - getLocalHeight())/2); } +void Node::drawDropShadow() +{ + if (effectSettings.dropShadow.size > 0) { + ofRectangle drawingRect = ofRectangle( + -effectSettings.dropShadow.size + effectSettings.dropShadow.position.x, + -effectSettings.dropShadow.size + effectSettings.dropShadow.position.y, + getWidth() + effectSettings.dropShadow.size * 2, + getHeight() + effectSettings.dropShadow.size * 2 + ); + + if (!fboDropShadow.isAllocated()) { + fboDropShadow.allocate( + getWidth() ,//+ effectSettings.dropShadow.size * 2, + getHeight() //+ effectSettings.dropShadow.size * 2 + ); + + fboDropShadow.begin(); + ofClear(0, 0); + + draw(); + fboDropShadow.end(); + effectDropShadow.update(fboDropShadow); + + } + + //ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_MULTIPLY); + effectDropShadow.update(fboDropShadow); + effectDropShadow.getShadow().draw(drawingRect); + ofEnableAlphaBlending(); + } +} + +void Node::onTouchDown(TouchEvent & event) { + if (touchDownFunc) { + touchDownFunc(event); + } +} + +void Node::onTouchMove(TouchEvent & event) { + if (touchMoveFunc) { + touchMoveFunc(event); + } +} + +void Node::onTouchUp(TouchEvent & event) { + if (touchUpFunc) { + touchUpFunc(event); + } +} + +void Node::onClick(TouchEvent & event) { + if (clickFunc) { + clickFunc(event); + } +} + void Node::touchDown(int id, TouchEvent* event) { if (bNodeAllowOneTouch && bNodeTouched && nodeCurrentTouchId!=id) { @@ -398,6 +596,10 @@ void Node::touchMove(int id, TouchEvent* event) if (bNodeAllowOneTouch && id != nodeCurrentTouchId) { return; } + + if (event->velocitySmoothed.length() > 200) { + isClickAllowed = false; + } #ifdef OFXUINODE_DEBUG debugBorderColor.a -= 10; @@ -419,16 +621,17 @@ void Node::touchUp(int id, TouchEvent* event) #ifdef USE_OFX_HISTORY_PLOT if (historyPlot) { delete historyPlot; - historyPlot = NULL; + historyPlot = nullptr; } #endif ofNotifyEvent(eventTouchUp, *event); - if (contains(event->position)) { + if (contains(event->position) && isClickAllowed) { ofNotifyEvent(eventClick, *event); } bNodeTouched = false; + isClickAllowed = true; } void Node::touchExit(int id, TouchEvent *event) @@ -455,12 +658,40 @@ void Node::touchEnter(int id, TouchEvent *event) ofNotifyEvent(eventTouchEnter, *event); } +const ofJson Node::toJson(glm::vec3 val) +{ + ofJson ret; + ret.push_back(val.x); + ret.push_back(val.y); + ret.push_back(val.z); + return ret; +} + +const ofJson Node::toJson(glm::vec2 val) +{ + ofJson ret; + ret.push_back(val.x); + ret.push_back(val.y); + return ret; +} + +const ofJson Node::toJson(glm::quat val) +{ + ofJson ret; + ret.push_back(val.w); + ret.push_back(val.x); + ret.push_back(val.y); + ret.push_back(val.z); + return ofJson(); +} + ofVec3f Node::toLocal(const ofVec3f &screenPoint) { #ifdef GLM_SWIZZLE //this version of OF is using GLM for math ops return (ofVec3f)screenPoint * glm::inverse(ofNode::getGlobalTransformMatrix()); #else - return (ofVec3f)screenPoint*ofNode::getGlobalTransformMatrix().getInverse(); + //return (ofVec3f)screenPoint*ofNode::getGlobalTransformMatrix().getInverse(); + return (ofVec3f)screenPoint * glm::inverse(ofNode::getGlobalTransformMatrix()); #endif } @@ -502,7 +733,7 @@ void Node::setVisible(bool visible) bool Node::getVisibleGlobally() const { - if (parent == NULL) { + if (parent == nullptr) { return bVisible; } else { @@ -543,7 +774,7 @@ void Node::setEnabled(bool enable) bool Node::getEnabledGlobally() const { - if (parent == NULL) { + if (parent == nullptr) { return bEnabled; } else { @@ -565,6 +796,22 @@ bool Node::contains(const ofVec3f &globalPoint) return true; } +void Node::setTouchDownFunction(std::function _func) { + touchDownFunc = _func; +} + +void Node::setTouchMoveFunction(std::function _func) { + touchMoveFunc = _func; +} + +void Node::setTouchUpFunction(std::function _func) { + touchUpFunc = _func; +} + +void Node::setClickFunction(std::function _func) { + clickFunc = _func; +} + void Node::addChild(Node *child, int insertAt, bool bMaintainChildGlobalTransform) { child->setParent(*this, bMaintainChildGlobalTransform); @@ -590,14 +837,14 @@ Node* Node::removeChild(Node *child, bool bMaintainChildGlobalTransform) } ofLogWarning("ofxInterface::Node::removeChild", "are you trying to remove a child that does not exist?"); - return NULL; + return nullptr; } Node* Node::removeChild(int index, bool bMaintainChildGlobalTransform) { if (index >= childNodes.size()) { ofLogWarning("ofxInterface::Node::removeChild", "are you trying to remove a child that does not exist?"); - return NULL; + return nullptr; } Node *child = childNodes[index]; @@ -617,17 +864,20 @@ void Node::sortChildren(const function& comp std::sort(childNodes.begin(), childNodes.end(), compareFunction); } -void Node::getSubTreeList(std::list& list) +void Node::getSubTreeList(std::list& list, bool returnAllGroupElements) { list.push_back(this); - for (int i=0; igetSubTreeList(list); + if (!bRenderChildrenInGroup) { + for (int i = 0; i < childNodes.size(); i++) + { + childNodes[i]->getSubTreeList(list); + } } + } -void Node::getVisibleSubTreeList(std::list& list) +void Node::getVisibleSubTreeList(std::list& list, bool returnAllGroupElements) { if (!getVisible()) { return; @@ -635,8 +885,10 @@ void Node::getVisibleSubTreeList(std::list& list) list.push_back(this); - for (int i=0; igetVisibleSubTreeList(list); + if (!bRenderChildrenInGroup) { + for (int i = 0; i < childNodes.size(); i++) { + childNodes[i]->getVisibleSubTreeList(list); + } } } @@ -683,7 +935,7 @@ void Node::placeNextTo(Node &comp, Node::Side side, float margin) } } -string Node::print(int depth) const +string Node::printItem(int depth) const { stringstream ss; @@ -693,7 +945,7 @@ string Node::print(int depth) const ss << depth<<"- "<print(depth+1); + ss << node->printItem(depth+1); } return ss.str(); @@ -710,5 +962,117 @@ float Node::getAngleTo(ofxInterface::Node *node) return atan2(translate.y, translate.x)*180/PI; } +ofJson Node::getSceneDescription() +{ + ofJson ret; + ret = getNodeJson(); + ofJson jChild; + for (auto& child:getChildren()){ + jChild.push_back(child->getSceneDescription()); + } + ret["children"] = jChild; + return ret; +} + +ofJson Node::getSceneDescription(vector attributes,bool onlyActiveNodes) +{ + return filterSceneDescription(getSceneDescription(), attributes , onlyActiveNodes); +} + +ofJson Node::getNodeJson() +{ + ofJson ret; + ret["nodeType"] = "Node"; + ret["name"] = getName(); + ret["size"] = toJson(getSize()); + ret["position"] = toJson(getPosition()); + ret["orientation"] = toJson(getOrientationQuat()); + ret["scale"] = toJson(getScale()); + ret["isVisible"] = getVisible(); + ret["isEnabled"] = getEnabled(); + ret["isReceivingTouch"] = getReceivingTouch(); + ret["plane"] = getPlane(); + if (dataIsJson) { + auto t = static_pointer_cast(data); + ret["customData"] = *t; + } + if (childNodes.size() > 0) { + ret["children"] = ofJson::array(); + for (auto& child : childNodes) { + ret["children"].push_back(child->getNodeJson()); + } + } + + return ret; +} + +ofJson Node::getNodeJsonFiltered(set keys) +{ + return filterJsonDescription(getNodeJson(),keys); +} + + +string Node::listActiveNodes(int depth) +{ + string ret; + if (getEnabled()) { + for (size_t i = 0; i < depth; i++){ + ret += " "; + } + ret += name + "\n"; + for (auto n : getChildren()) { + ret += n->listActiveNodes(depth+1); + } + } + return ret; +} + +EffectSettings Node::getEffectSettings() +{ + return effectSettings; +} + +ofJson Node::filterSceneDescription(ofJson desc, vector attributes, bool onlyActiveNodes) +{ + ofJson ret; + if (onlyActiveNodes == false || (onlyActiveNodes == true && getEnabled()) ) { + for (auto& attr : attributes) { + if (desc[attr] != nullptr) ret[attr] = desc[attr]; + } + + if (desc["children"] != nullptr && desc["children"].size() > 0) { + for (auto& child : desc["children"]) { + ret["children"].push_back(filterSceneDescription(child, attributes, onlyActiveNodes)); + } + } + } + return ret; +} + +ofJson Node::filterJsonDescription(ofJson desc, set keys) +{ + ofJson ret = ofJson(); + bool hasChild = false; + for (ofJson::iterator it = desc.begin(); it != desc.end(); ++it) { + if (keys.find(it.key()) != keys.end()) { + if (it.key() == "children") { + hasChild = true; + } + else { + ret[it.key()] = it.value(); + } + } + } + + if (hasChild) { + ret["children"] = ofJson::array(); + for (auto& child : desc["children"]) { + ret["children"].push_back(filterJsonDescription(child, keys)); + } + } + + return ret; +} + } // namespace diff --git a/src/Node.h b/src/Node.h index 069b81c..9bb19c6 100644 --- a/src/Node.h +++ b/src/Node.h @@ -12,6 +12,7 @@ #include #include "ofMain.h" #include "TouchEvent.h" +#include "EffectDropShadow.h" // you can comment this to save a little (very little) #define OFXUINODE_DEBUG @@ -22,6 +23,21 @@ namespace ofxInterface { + + + struct EffectSettings { + EffectDropShadowSettings dropShadow; + }; + + struct NodeSettings { + string name; + ofVec2f position; + ofVec2f size; + bool renderClip = false; + bool isActive = true; + float plane = 10; + EffectSettings effects; +}; class Node : public ofNode { @@ -34,6 +50,8 @@ class Node : public ofNode Node(const Node& mom); virtual Node* clone(); + void setup(NodeSettings settings); + /****** * Node Names: * @@ -43,6 +61,7 @@ class Node : public ofNode const std::string& getName() const { return name; } // search the tree for a node with a specific name, searchDepth of -1 means search all the way down Node* getChildWithName(const std::string& searchName, int searchDepth = -1) const; + vector getChildrenWithName(const std::string& searchName, int searchDepth = -1) const; Node* getParentWithName(const std::string& searchName, int searchDepth = -1) const; /****** @@ -65,8 +84,10 @@ class Node : public ofNode // functions to override // - virtual void update(float dt) {} // please override with update code - virtual void draw() {}; // please override! draw your object in local space + virtual void update(float dt) {}; // please override with update code + virtual void draw() {} ; // please override! draw your object in local space + virtual void preDraw() {}; // affects group rendering, executed before the draw function + virtual void postDraw() {}; // affects group rendering, executed after the draw function // for debugging virtual void drawDebug(); // debug debugging stuff (will be called by renderDebug) @@ -89,6 +110,13 @@ class Node : public ofNode ofEvent eventTouchEnter; ofEvent eventClick; + // interaction functions (register lambda functions here) + void setTouchDownFunction(std::function _func); + void setTouchMoveFunction(std::function _func); + void setTouchUpFunction(std::function _func); + void setClickFunction(std::function _func); + + // use this to enforce only one touch at a time (single touch) void setAllowOnlyOneTouch(bool set) { bNodeAllowOneTouch = set; } bool isTouched() { return bNodeTouched; } @@ -107,6 +135,10 @@ class Node : public ofNode */ void render(bool forceAll = false); void renderDebug(bool forceAll = false); // same as render but calls drawDebug instead of draw. + + //TODO : description + void renderDynamic(bool forceAll = false); + void renderGroups(bool forceAll = false); /****** * call the 'update' function of !!visible!! children @@ -125,6 +157,10 @@ class Node : public ofNode bool getRenderClip() { return bClipRender; } bool getGlobalRenderClip(); ofRectangle getRenderClipRect(); + // if group is activated then all children are rendered afterwards and using the plane of this node (enables group masking) + void setRenderChildrenInGroup(bool set) {bRenderChildrenInGroup = set;}; + bool getRenderChildrenInGroup() { return bRenderChildrenInGroup; }; + // override these functions if you use some other renderer such as NanoVG virtual void enableScissor(const ofRectangle& rect) { enableScissor(rect.x, rect.y, rect.width, rect.height); } virtual void enableScissor(float x, float y, float w, float h); @@ -251,9 +287,9 @@ class Node : public ofNode } } // returns a list representation of all childNodes in sub graph - void getSubTreeList(std::list& list); + void getSubTreeList(std::list& list, bool returnAllGroupElements = true); // returns a list of visible elements in subtree - void getVisibleSubTreeList(std::list& list); + void getVisibleSubTreeList(std::list& list, bool returnAllGroupElements = true); // returns a list of enabled elements in subtree void getEnabledSubTreeList(std::list& list); @@ -339,7 +375,8 @@ class Node : public ofNode // use this to hold any data - void setData(shared_ptr _data) { data = _data; } + template + void setData(shared_ptr _data); shared_ptr getData() { return data; } //check for leaks @@ -357,7 +394,7 @@ class Node : public ofNode ofEvent eventDestroy; // send this event in the destructor - string print(int depth=0) const; + string printItem(int depth=0) const; template void addEventListener(const string& eventName, ListenerClass * listener, void (ListenerClass::*listenerMethod)(ArgType&), bool recursive=false) @@ -408,8 +445,26 @@ class Node : public ofNode ofVec3f getTranslationTo(Node* node); float getAngleTo(Node* node); + /****** + * Exporting + * + * export the node as json + * + */ + + ofJson getSceneDescription(); + ofJson getSceneDescription(vector attributes, bool onlyActiveNodes = false); + virtual ofJson getNodeJson(); + virtual ofJson getNodeJsonFiltered(set keys); + string listActiveNodes(int depth = 0); + + // effect settings + EffectSettings getEffectSettings(); protected: + ofJson filterSceneDescription(ofJson desc, vector attributes,bool onlyActiveNodes); + ofJson filterJsonDescription(ofJson desc, set keys); + std::string name; ofVec2f size; @@ -418,6 +473,7 @@ class Node : public ofNode bool bReceivingTouch; bool bClipRender; bool bClipTouch; + bool bRenderChildrenInGroup = false; vector childNodes; @@ -427,6 +483,7 @@ class Node : public ofNode // hold custom data shared_ptr data; + bool dataIsJson = false; // debug stuff @@ -443,6 +500,16 @@ class Node : public ofNode static ofColor touchEnterNodeColor; #endif + + + // effects + EffectSettings effectSettings; + + // drop shadow + ofFbo fboDropShadow; + EffectDropShadow effectDropShadow; + void drawDropShadow(); + private: template ofEvent* getEventForName(const string& eventName) @@ -461,12 +528,27 @@ class Node : public ofNode } } + // lambda functions here + std::function touchDownFunc; + std::function touchMoveFunc; + std::function touchUpFunc; + std::function clickFunc; + + void onTouchDown(TouchEvent& event); + void onTouchMove(TouchEvent& event); + void onTouchUp(TouchEvent& event); + void onClick(TouchEvent& event); + bool bSendDestroy; bool bNodeAllowOneTouch; bool bNodeUpdateWhenHidden; bool bNodeTouched; int nodeCurrentTouchId; + bool isClickAllowed = true; + + + #ifdef OLD_SORT /****** * sameDepthOffset: @@ -485,8 +567,27 @@ class Node : public ofNode void touchUp(int id, TouchEvent* event); void touchExit(int id, TouchEvent* event); void touchEnter(int id, TouchEvent* event); + + + //helper functions for export json + const ofJson toJson(glm::vec3 val); + const ofJson toJson(glm::vec2 val); + const ofJson toJson(glm::quat val); }; +template +inline void Node::setData(shared_ptr _data) +{ + if (ofIsStringInString(typeid(_data).name(), "class std::shared_ptr{ style.color.r,style.color.g,style.color.b,style.color.a }; + ret["fontId"] = style.fontID; + ret["fontSize"] = style.fontSize; + ret["lineHeightMult"] = style.lineHeightMult; + ret["spacing"] = style.spacing; + return ret; + } + ofJson compositionToLog(ofJson sceneDescription, ofJson metaData) + { + ofJson ret = ofJson(); + if (!metaData["background"].is_null()) { + auto path = ofSplitString(metaData["background"].get(), "../").back(); + ret["background"] = path; + } + if (!metaData["overlay"].is_null()) { + auto path = ofSplitString(metaData["overlay"].get(), "../").back(); + ret["overlay"] = path; + } + if (!metaData["isPrint"].is_null()) { + ret["isPrint"] = metaData["isPrint"].get(); + } + if (!metaData["sharing"].is_null()) { + ret["sharing"] = metaData["sharing"].get(); + } + if (!metaData["itemId"].is_null()) { + ret["itemId"] = metaData["itemId"].get(); + } + if (!metaData["furtherUse"].is_null()) { + ret["furtherUse"] = metaData["furtherUse"].get(); + } + + ofJson icons = ofJson(); + ofJson texts = ofJson(); + + for (auto& objects : sceneDescription["children"][0]["children"]) { + if (objects["name"] == "objects" && !objects["children"].is_null()) { + for (auto& elem : objects["children"]) { + if (elem["nodeType"] == "TextureNode") { + auto path = ofSplitString(elem["customData"]["file"].get(), "../").back(); + icons.push_back(path); + } + else if (elem["nodeType"] == "TextInputOfTTF") { + ofJson text = ofJson(); + text["fontId"] = ofSplitString(elem["style"].get(), "/").back(); + text["text"] = elem["text"]; + texts.push_back(text); + } + + } + } + } + if (!icons.is_null()) + { + ret["icons"] = icons; + } + if (!texts.is_null()) { + ret["texts"] = texts; + } + return ret; + } + } +} \ No newline at end of file diff --git a/src/NodeToJsonHelper.h b/src/NodeToJsonHelper.h new file mode 100644 index 0000000..a6e3ae0 --- /dev/null +++ b/src/NodeToJsonHelper.h @@ -0,0 +1,11 @@ +#pragma once +#include "ofMain.h" +#include "ofxFontStash2.h" + +namespace reddo{ + namespace NodeToJson { + ofJson styleToJson(ofxFontStash2::Style style); + ofJson compositionToLog(ofJson sceneDescription, ofJson metaData); + } +} + diff --git a/src/components/BitmapTextButton.cpp b/src/components/BitmapTextButton.cpp deleted file mode 100644 index 8b726ee..0000000 --- a/src/components/BitmapTextButton.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// BitmapTextButton.cpp -// ofxInterface -// -// Created by Gal Sasson on 1/19/15. -// -// - -#include "BitmapTextButton.h" - -namespace ofxInterface -{ - -void BitmapTextButton::setup(const string &_label) -{ - label = _label; - setSize(label.length()*8 + 10, 20); - setName(label); - - bDrawBorder = true; - bDrawBackground = true; - - borderColor = ofColor(0); - bgColor = ofColor(255); - labelColor = ofColor(0); -} - -void BitmapTextButton::draw() -{ - if (bDrawBackground) { - ofSetColor(bgColor * (isTouched()?0.5:1)); - ofFill(); - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } - - ofSetColor(labelColor); - ofDrawBitmapString(label, 5, getHeight()-5); - - if (bDrawBorder) { - ofSetColor(borderColor); - ofNoFill(); - ofSetLineWidth(1); - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } -} - - -} // namespace diff --git a/src/components/BitmapTextButton.h b/src/components/BitmapTextButton.h deleted file mode 100644 index 7b15cec..0000000 --- a/src/components/BitmapTextButton.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// BitmapTextButton.h -// AnimationBreaker -// -// Created by Gal Sasson on 1/19/15. -// -// - -#ifndef __ofxInterface__BitmapTextButton__ -#define __ofxInterface__BitmapTextButton__ - -#include -#include "ofMain.h" - -#include "ofxInterface.h" - -namespace ofxInterface -{ - -class BitmapTextButton : public Node -{ -public: - - void setup(const string& label); - - void setBackground(bool set) { bDrawBackground = set; } - void setBorder(bool set) { bDrawBorder = set; } - - void setLabelColor(const ofColor& c) { labelColor = c; } - void setBGColor(const ofColor& c) { bgColor = c; } - void setBorderColor(const ofColor& c) { borderColor = c; } - - void draw(); - -private: - - bool bDrawBackground; - bool bDrawBorder; - - ofColor borderColor; - ofColor bgColor; - ofColor labelColor; - - string label; -}; - -} // namespace - -#endif /* defined(__ofxUINode__BitmapTextButton__) */ diff --git a/src/components/ColorPanel.cpp b/src/components/ColorPanel.cpp new file mode 100644 index 0000000..47c6fa4 --- /dev/null +++ b/src/components/ColorPanel.cpp @@ -0,0 +1,221 @@ +#include "ColorPanel.h" + +namespace ofxInterface { + + +void ColorPanel::setup(ofColor color, float w, float h) +{ + ColorPanelSettings s; + s.size = ofVec2f(w, h); + s.colors.front() = color; + s.bDrawBackground = true; + setup(s); +} + +void ColorPanel::setup(ColorPanelSettings settings) { + Node::setup(settings); + borderWidth = settings.strokeWidth; + bDrawBackground = settings.bDrawBackground; + borderRadius = settings.borderRadius; + strokeColor = settings.strokeColor; + + if (settings.colors.size() > 0) { + colors.clear(); + } + for (size_t i = 0; i < settings.colors.size();++i) { + colors.push_back(settings.colors[i]); + if (i < settings.gradientColorPositions.size()) { + gradientColorPositions.push_back(settings.gradientColorPositions[i]); + } + else { + if (i == 0) { + gradientColorPositions.push_back(0.0); + } + else if (i == 1) { + gradientColorPositions.push_back(1.0); + } + + } + } + gradientDirection = settings.gradientDirection; + + shader.push_back(getGradientShader2Colors()); + shader.push_back(getGradientShader3Colors()); + + + initShape(); + + ofAddListener(eventNodeSizeChanged,this, &ColorPanel::onNodeSizeChanged); + +} + +Node* ColorPanel::clone() +{ + auto ret = new ColorPanel(*this); + return ret; +} + +ofColor ColorPanel::getColor() { + return colors.front(); +} + +ofColor ColorPanel::getStrokeColor() { + return strokeColor; +} + +void ColorPanel::setStrokeColor(const ofColor & c) { + strokeColor = c; +} + +bool ColorPanel::isDrawBackground() { + return bDrawBackground; +} + +void ColorPanel::setDrawBackground(bool set) { + bDrawBackground = set; +} + +float ColorPanel::getBorderWidth() { + return borderWidth; +} + +void ColorPanel::setBorderWidth(float set) { + borderWidth = set; +} + +float ColorPanel::getRoundedEdge() { + return borderRadius; +} + +void ColorPanel::setRoundedEdge(float ang) { + borderRadius = ang; +} + +void ColorPanel::onNodeSizeChanged(Node& n) +{ + initShape(); +} + +void ColorPanel::initShape() +{ + shape.clear(); + outline.clear(); + shape.setFilled(colors.front().a > 0); + outline.setStrokeWidth(borderWidth); + shape.setFillColor(colors.front()); + outline.setStrokeColor(strokeColor); + shape.setUseShapeColor(true); + outline.setUseShapeColor(true); + + if (borderRadius > 0) { + shape.rectRounded(borderWidth, borderWidth, getWidth() - getWidth() * borderWidth, getHeight() - 2 * borderWidth, borderRadius); + outline.rectRounded(0, 0, getWidth(), getHeight(), borderRadius); + outline.rectRounded(borderWidth, borderWidth, getWidth() - getWidth() * borderWidth, getHeight() - 2 * borderWidth, borderRadius); + } + else { + shape.rectangle(borderWidth, borderWidth, getWidth() - 2 * borderWidth, getHeight() - 2 * borderWidth); + outline.rectangle(0, 0, getWidth(), getHeight()); + outline.rectangle(borderWidth, borderWidth, getWidth() - 2 * borderWidth, getHeight() - 2 * borderWidth); + } + + // shader + if (colors.size() > 1) { + fbo.allocate(getWidth(), getHeight()); + + ofEnableBlendMode(OF_BLENDMODE_DISABLED); + fbo.begin(); + + ofClear(0, 0); + int nShader = 0; + if (colors.size() == 3) { + nShader = 1; + } + shader[nShader].begin(); + + + + ofVec2f pEnd = angleToBorderPoint(gradientDirection); + ofVec2f pStart = angleToBorderPoint(fmod(gradientDirection+180,360)); + ofVec2f dir = pEnd - pStart; + pStart += dir * gradientColorPositions.front(); + pEnd = pStart + dir * gradientColorPositions.back(); + + + + + shader[nShader].setUniform2f("begin", pStart); + shader[nShader].setUniform2f("end", pEnd); + + + + shader[nShader].setUniform2f("size", getSize()); + shader[nShader].setUniform4f("color1", colorToVec4(colors[0])); + shader[nShader].setUniform4f("color2", colorToVec4(colors[1])); + if (nShader == 2) { + shader[nShader].setUniform1f("mid", 0.66f); + shader[nShader].setUniform4f("color3", colorToVec4(colors[2])); + } + + + /*if (roundAngle > 0) { + ofDrawRectRounded(0, 0, getWidth(), getHeight(), roundAngle); + } + else {*/ + ofDrawRectangle(0, 0, getWidth(), getHeight()); + //} + + shader[nShader].end(); + + fbo.end(); + ofEnableBlendMode(OF_BLENDMODE_ALPHA); + ofSetColor(255); + fbo.draw(0, 0); + } + +} + +ofVec2f ColorPanel::angleToBorderPoint(float angle) +{ + ofVec2f d = angleToDirVector(angle); + ofVec2f p = ofVec2f(0.5, 0.5); + + if (angle < 45 || angle >315) { + float r = (1.0 - p.x) / d.x; + return ofVec2f(1.0, p.y + r * d.y); + } + else if (angle < 135) { + float r = (1.0 - p.y) / d.y; + return ofVec2f(p.x + r * d.x,1.0); + } + else if (angle < 225) { + float r = (0 - p.x) / d.x; + return ofVec2f(0, p.y + r * d.y); + } + else { + float r = (0 - p.y) / d.y; + return ofVec2f(p.x + r * d.x, 0); + } +} + +ofVec2f ColorPanel::angleToDirVector(float angle) +{ + ofVec2f vDirE = ofVec2f(1, 0); + vDirE.rotate(angle); + return vDirE; +} + +void ColorPanel::draw() +{ + outline.draw(); + if (colors.size() == 1) { + shape.draw(); + } + else { + fbo.draw(borderWidth, borderWidth, getWidth() - getWidth() * borderWidth, getHeight() - 2 * borderWidth); + } + +} + + + +} // namespace \ No newline at end of file diff --git a/src/components/ColorPanel.h b/src/components/ColorPanel.h new file mode 100644 index 0000000..3bf39e8 --- /dev/null +++ b/src/components/ColorPanel.h @@ -0,0 +1,68 @@ +#pragma once + +#include "ofMain.h" + +#include "Node.h" +#include "GradientShader.h" + +namespace ofxInterface { + struct ColorPanelSettings : NodeSettings{ + float strokeWidth = 0; + bool bDrawBackground = true; + float borderRadius = 0; + + vector colors = {ofColor(0)}; + ofColor strokeColor = ofColor(0); + float gradientDirection = 0; + vector gradientColorPositions; + }; + + class ColorPanel : public ofxInterface::Node + { + public: + void setup(ofColor color, float w, float h); + void setup(ColorPanelSettings settings); + virtual Node* clone() override; + + void draw(); + + ofColor getColor(); + void setColor(const ofColor& c) { colors[0] = c; shape.setFillColor(c); } + + ofColor getStrokeColor(); + void setStrokeColor(const ofColor& c); + + bool isDrawBackground(); + void setDrawBackground(bool set); + + float getBorderWidth(); + void setBorderWidth(float set); + + float getRoundedEdge(); + void setRoundedEdge(float ang); + + void onNodeSizeChanged(Node& n); + + protected: + void initShape(); + ofVec2f angleToBorderPoint(float angle); + ofVec2f angleToDirVector(float angle); + + float borderWidth; + bool bDrawBackground = true; + float borderRadius; + + vector colors; + ofColor strokeColor; + float gradientDirection; + vector gradientColorPositions; + + ofPath shape; + ofPath outline; + + vector shader; + ofFbo fbo; + }; + +} + diff --git a/src/components/Label.cpp b/src/components/Label.cpp new file mode 100644 index 0000000..038d8cc --- /dev/null +++ b/src/components/Label.cpp @@ -0,0 +1,115 @@ +#include "Label.h" + + +namespace ofxInterface { + Label::Label() :Node() + { + + } + + Label::Label(const Label & mom):Node(mom) + { + font = mom.font; + style = mom.style; + text = mom.text; + horzAlignment = mom.horzAlignment; + isDropshadow = mom.isDropshadow; + shadow = mom.shadow; + shadowPos = mom.shadowPos; + } + + + Label::~Label() + { + } + + Node * Label::clone() + { + return new Label(*this); + } + + void Label::draw() + { + auto bounds = font->getTextBounds(text, style, 0, 0); + float dy = bounds.height*0.5; + + bounds.y += dy; + + // since fontstash uses its own clip functions, we need to manually disable the draw + // masks out the text completely, when out of clip + // need to find a more appropriate function + //ofRectangle boundsGlobal( toGlobal(ofVec3f(bounds.x,bounds.y)),toGlobal(ofVec3f(bounds.width,bounds.height)) ); + ofRectangle boundsGlobal( toGlobal(ofVec3f(bounds.x,bounds.y)),bounds.width,bounds.height ); + boundsGlobal.y -= getHeight() * 0.5; + //boundsGlobal.width -= boundsGlobal.x; + //boundsGlobal.height -= boundsGlobal.y; + + auto cl = getGlobalRenderClip(); + auto in = getRenderClipRect().intersects(boundsGlobal); + if (!getGlobalRenderClip() || getRenderClipRect().intersects(boundsGlobal)) { + if (isDropshadow) { + font->drawColumn(text, shadow, shadowPos.x, shadowPos.y + dy, getWidth(), horzAlignment); + } + font->drawColumn(text, style, 0, dy, getWidth(), horzAlignment); + } + + + } + + void ofxInterface::Label::setup(LabelSettings s) + { + Node::setup(s); + font = s.font; + style = s.style; + text = s.text; + horzAlignment = s.horzAlignment; + setShadow(s.isDropshadow, s.shadowSize, s.shadowPos.x, s.shadowPos.y, s.shadowColor); + + } + + void Label::setText(string text_) + { + text = text_; + } + + string Label::getText() + { + return text; + } + + void Label::setAlignment(ofAlignHorz alignment_) + { + horzAlignment = alignment_; + } + void Label::setShadowEnabled(bool isEnabled) + { + isDropshadow = isEnabled; + } + void Label::setShadow(bool isEnabled, float w, float x, float y, ofColor color) { + isDropshadow = isEnabled; + shadow = style; + shadow.blur = w; + shadow.color = color; + shadowPos = ofVec2f(x, y); + } + void Label::setFontSize(float size) { + style.fontSize = size; + shadow.fontSize = size; + } + float Label::getFontSize() { + return style.fontSize; + } + ofxFontStash2::Style Label::getFontStyle() + { + return style; + } + + void Label::setColor(ofColor color) + { + style.color = color; + } + ofColor Label::getColor() + { + return style.color; + } +} \ No newline at end of file diff --git a/src/components/Label.h b/src/components/Label.h new file mode 100644 index 0000000..dfd12f9 --- /dev/null +++ b/src/components/Label.h @@ -0,0 +1,54 @@ +#pragma once +#include "Node.h" +#include "ofxFontStash2.h" + +namespace ofxInterface { + struct LabelSettings :NodeSettings{ + shared_ptr font; + ofxFontStash2::Style style; + string text; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + + bool isDropshadow = false; + ofxFontStash2::Style shadow; + ofVec2f shadowPos; + float shadowSize; + ofColor shadowColor; + }; + + class Label : + public Node + { + public: + Label(); + Label(const Label& mom); + ~Label(); + + virtual Node* clone() override; + + virtual void draw(); + virtual void setup(LabelSettings s); + void setText(string text); + string getText(); + void setAlignment(ofAlignHorz horzAlignment); + void setShadowEnabled(bool isEnabled); + void setShadow(bool isEnabled, float w = 0, float x= 0, float y = 0, ofColor color = ofColor(0)); + void setFontSize(float size); + float getFontSize(); + ofxFontStash2::Style getFontStyle(); + void setColor(ofColor color); + ofColor getColor(); + + + private: + shared_ptr font; + ofxFontStash2::Style style; + string text; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + + bool isDropshadow = false; + ofxFontStash2::Style shadow; + ofVec2f shadowPos; + }; +} + diff --git a/src/components/LambdaView.cpp b/src/components/LambdaView.cpp index 209de57..280751e 100644 --- a/src/components/LambdaView.cpp +++ b/src/components/LambdaView.cpp @@ -14,9 +14,6 @@ namespace ofxInterface LambdaView::LambdaView(const string& name) { setName(name); - ofAddListener(eventTouchDown, this, &LambdaView::onTouchDown); - ofAddListener(eventTouchMove, this, &LambdaView::onTouchMove); - ofAddListener(eventTouchUp, this, &LambdaView::onTouchUp); } void LambdaView::setDrawFunction(std::function _func) @@ -34,21 +31,6 @@ void LambdaView::setUpdateFunction(std::function _func) updateFunc = _func; } -void LambdaView::setTouchDownFunction(std::function _func) -{ - touchDownFunc = _func; -} - -void LambdaView::setTouchMoveFunction(std::function _func) -{ - touchMoveFunc = _func; -} - -void LambdaView::setTouchUpFunction(std::function _func) -{ - touchUpFunc = _func; -} - void LambdaView::setContainsFunction(std::function _func) { containsFunc = _func; @@ -85,25 +67,4 @@ bool LambdaView::contains(const ofVec3f& global) } } -void LambdaView::onTouchDown(TouchEvent& event) -{ - if (touchDownFunc) { - touchDownFunc(event); - } -} - -void LambdaView::onTouchMove(TouchEvent& event) -{ - if (touchMoveFunc) { - touchMoveFunc(event); - } -} - -void LambdaView::onTouchUp(TouchEvent& event) -{ - if (touchUpFunc) { - touchUpFunc(event); - } -} - } \ No newline at end of file diff --git a/src/components/LambdaView.h b/src/components/LambdaView.h index 5e147bb..e15c7a6 100644 --- a/src/components/LambdaView.h +++ b/src/components/LambdaView.h @@ -22,9 +22,6 @@ class LambdaView : public Node void setDrawFunction(std::function _func); void setDrawDebugFunction(std::function _func); void setUpdateFunction(std::function _func); - void setTouchDownFunction(std::function _func); - void setTouchMoveFunction(std::function _func); - void setTouchUpFunction(std::function _func); void setContainsFunction(std::function _func); void update(float dt); void draw(); @@ -35,14 +32,7 @@ class LambdaView : public Node std::function drawFunc; std::function drawDebugFunc = [&](){Node::drawDebug();}; std::function updateFunc; - std::function touchDownFunc; - std::function touchMoveFunc; - std::function touchUpFunc; std::function containsFunc; - - void onTouchDown(TouchEvent& event); - void onTouchMove(TouchEvent& event); - void onTouchUp(TouchEvent& event); }; } diff --git a/src/components/ModalElement.cpp b/src/components/ModalElement.cpp new file mode 100644 index 0000000..9e81d1e --- /dev/null +++ b/src/components/ModalElement.cpp @@ -0,0 +1,129 @@ +#include "ModalElement.h" + + +namespace ofxInterface { + ModalElement::ModalElement() + { + ofAddListener(eventClick, this, &ModalElement::onClicked, 100); + ofAddListener(eventTouchDown, this, &ModalElement::onTouchDown, 100); + ofAddListener(eventTouchUp, this, &ModalElement::onTouchUp, 100); + isSelected.addListener(this, &ModalElement::onStateChanged); + } + + ModalElement::ModalElement(const ModalElement & mom) : Node(mom) + { + colorActive = mom.colorActive; + colorSelected = mom.colorSelected; + colorInactive = mom.colorInactive; + type = mom.type; + isSelected = mom.isSelected.get(); + isSelected.addListener(this, &ModalElement::onStateChanged); + ofAddListener(eventClick, this, &ModalElement::onClicked, 100); + ofAddListener(eventTouchDown, this, &ModalElement::onTouchDown, 100); + ofAddListener(eventTouchUp, this, &ModalElement::onTouchUp, 100); + } + + + ModalElement::~ModalElement() + { + } + + Node * ModalElement::clone() + { + return new ModalElement(*this); + } + + void ModalElement::setup(ModalElementSettings s) + { + Node::setup(s); + type = s.type; + colorActive = s.colorActive; + colorInactive = s.colorInactive; + colorSelected = s.colorSelected; + + + } + + void ModalElement::draw() + { + //if (type == ofxInterface::BUTTON && !isTouched() && isSelected) { + // isSelected = false; + //} + + if (getEnabled()) { + ofSetColor(colorActive); + ofDrawRectangle(0, 0, getWidth(), getHeight()); + if (isSelected) { + ofSetColor(colorSelected); + ofDrawRectangle(2, 2, getWidth()-4, getHeight()-4); + } + } else { + ofSetColor(colorInactive); + ofDrawRectangle(0, 0, getWidth(), getHeight()); + } + + + + } + void ModalElement::onClicked(TouchEvent & e) + { + switch (type) { + case ofxInterface::BUTTON: + break; + case ofxInterface::CHECKER: + isSelected = !isSelected; + break; + case ofxInterface::RADIO: + if (!isSelected) { + isSelected = true; + } + break; + default: + break; + } + } + + void ModalElement::onTouchDown(TouchEvent & e) { + if (type == BUTTON) { + isSelected = true; + } + } + + void ModalElement::onTouchUp(TouchEvent & e) { + if (type == BUTTON) { + isSelected = false; + } + } + + void ModalElement::setModalType(ModalType type_) { + type = type_; + } + + ModalType ModalElement::getModalType() { + return type; + } + + ofJson ModalElement::getNodeJson() + { + auto ret = Node::getNodeJson(); + ret["nodeType"] = "ModalElement"; + + switch (type) { + case BUTTON: + ret["modalType"] = "Button"; + break; + case CHECKER: + ret["modalType"] = "Checker"; + break; + case RADIO: + ret["modalType"] = "Radio"; + break; + } + ret["colorActive"] = vector{ colorActive.r,colorActive.g,colorActive.b,colorActive.a }; + ret["colorSelected"] = vector{ colorSelected.r,colorSelected.g,colorSelected.b,colorSelected.a }; + ret["colorInactive"] = vector{ colorInactive.r,colorInactive.g,colorInactive.b,colorInactive.a }; + + return ret; + } + +} \ No newline at end of file diff --git a/src/components/ModalElement.h b/src/components/ModalElement.h new file mode 100644 index 0000000..8279588 --- /dev/null +++ b/src/components/ModalElement.h @@ -0,0 +1,49 @@ +#pragma once +#include "Node.h" + +namespace ofxInterface { + enum ModalType { + BUTTON, + CHECKER, + RADIO + }; + + struct ModalElementSettings : NodeSettings{ + ModalType type = BUTTON; + ofColor colorActive = ofColor(200); + ofColor colorSelected = ofColor(100); + ofColor colorInactive = ofColor(50); + }; + + class ModalElement : public Node + { + public: + ModalElement(); + ModalElement(const ModalElement& mom); + ~ModalElement(); + + virtual Node* clone() override; + + void setup(ModalElementSettings s); + virtual void draw(); + + void onClicked(TouchEvent& e); + void onTouchDown(TouchEvent& e); + void onTouchUp(TouchEvent& e); + virtual void onStateChanged(bool& isActive) {}; + ofParameter isSelected = false; + + void setModalType(ModalType type); + ModalType getModalType(); + + ofColor colorActive; + ofColor colorSelected; + ofColor colorInactive; + + ofJson getNodeJson() override; + + private: + ModalType type; + + }; +} diff --git a/src/components/ScrollableContainer.cpp b/src/components/ScrollableContainer.cpp new file mode 100644 index 0000000..abb5859 --- /dev/null +++ b/src/components/ScrollableContainer.cpp @@ -0,0 +1,330 @@ +#include "ScrollableContainer.h" + + +namespace ofxInterface { + ScrollableContainer::ScrollableContainer():Node() + { + } + + + ScrollableContainer::~ScrollableContainer() + { + } + + void ScrollableContainer::setup(ScrollableContainerSettings s) { + Node::setup(s); + setRenderClip(true); + setTouchClip(true); + + scrollableArea = new Node(); + scrollableArea->setName("scrollableArea"); + scrollableArea->setSize(s.sizeScrollableArea); + addChild(scrollableArea); + + bgColor = s.bgColor; + scrollActiveColor = s.scrollActiveColor; + scrollInactiveColor = s.scrollInactiveColor; + + ColorPanelSettings cs; + cs.colors.front() = s.scrollActiveColor; + cs.plane = 10; + + slider.insert(make_pair("w", new ColorPanel())); + slider["w"]->setup(cs); + slider.insert(make_pair("h", new ColorPanel())); + slider["h"]->setup(cs); + + cs.colors.front() = s.scrollInactiveColor; + slider.insert(make_pair("wInactive", new ColorPanel())); + slider["wInactive"]->setup(cs); + slider.insert(make_pair("hInactive", new ColorPanel())); + slider["hInactive"]->setup(cs); + + baseSize = getSize(); + + addChild(slider["wInactive"]); + addChild(slider["hInactive"]); + addChild(slider["w"]); + addChild(slider["h"]); + for (auto& s : slider) { + s.second->setEnabled(false); + } + + ofAddListener(eventChildAdded, this, &ScrollableContainer::onChildAdded); + ofAddListener(scrollableArea->eventChildRemoved, this, &ScrollableContainer::onChildRemoved); + ofAddListener(scrollableArea->eventTouchMove, this, &ScrollableContainer::onTouchMove); + ofAddListener(scrollableArea->eventTouchUp, this, &ScrollableContainer::onTouchUp); + ofAddListener(scrollableArea->eventTouchDown, this, &ScrollableContainer::onTouchDown); + + edgeAnimation.setCurve(AnimCurve::SWIFT_GOOGLE); + edgeAnimation.setAnimFinishedLambda([this]() { + if (animationReady) { + edgeAnimation.animateTo(finalPoint); + animationReady = false; + } + }); + + + + } + + + void ScrollableContainer::draw() + { + + // animate elements if scrolled on edge + if (edgeAnimation.isAnimating()) { + edgeAnimation.update(ofGetLastFrameTime()); + scrollableArea->setPosition(edgeAnimation.getCurrentPosition()); + + + // animate elements right after touchUp + } + if (isAutoScroll) { + currentVelocity *= friction; + + ofVec2f newPos = ofVec2f(scrollableArea->getPosition()) + currentVelocity* ofGetLastFrameTime(); + + newPos.x = ofClamp(newPos.x, getWidth() - scrollableArea->getWidth(), 0); + newPos.y = ofClamp(newPos.y, getHeight() - scrollableArea->getHeight(), 0); + + scrollableArea->setPosition(newPos); + + if (abs(currentVelocity.x) < 1 && abs(currentVelocity.y) < 1) { + isAutoScroll = false; + } + else if( ((newPos.x == 0 || newPos.x == getWidth() - scrollableArea->getWidth()) && + (newPos.y == 0 || newPos.y == getHeight() - scrollableArea->getHeight()))) { + isAutoScroll = false; + + edgeAnimation.setPosition(scrollableArea->getPosition()); + edgeAnimation.setDuration(0.15); + + ofVec2f d; + d.x = currentVelocity.x < 0 ? -50 : 50; + d.y = currentVelocity.y < 0 ? -50 : 50; + if (getWidth() == scrollableArea->getWidth()) { + d.x = 0; + } + if (getHeight() == scrollableArea->getHeight()) { + d.y = 0; + } + ofVec2f pos = ofVec2f(scrollableArea->getPosition()) + d; + pos.x = ofClamp(pos.x,-1*(scrollableArea->getWidth() - getWidth()), 0); + pos.y = ofClamp(pos.y,-1*(scrollableArea->getHeight() - getHeight()), 0); + + edgeAnimation.animateTo(pos); + finalPoint = scrollableArea->getPosition(); + animationReady = false; + } + } + + ofSetColor(bgColor); + ofDrawRectangle(0, 0, getWidth(), getHeight()); + + updateSlider(); + + } + + + Node * ScrollableContainer::getScrollableArea() { + return scrollableArea; + } + + void ScrollableContainer::onTouchDown(TouchEvent & event) { + edgeAnimation.reset(); + } + + void ScrollableContainer::onTouchUp(TouchEvent & event) { + // use the event velocity to simulate an impulse + float dMaxImpulse = 1000; + ofVec3f velocityModifier; + + velocityModifier.x = ofxeasing::map_clamp(abs(event.velocitySmoothed.x), 100, maxVelocity, 0, dMaxImpulse, &ofxeasing::exp::easeOut); + if (event.velocitySmoothed.x < 0) { + velocityModifier.x *= -1; + } + velocityModifier.y = ofxeasing::map_clamp(abs(event.velocitySmoothed.y), 100, maxVelocity, 0, dMaxImpulse, &ofxeasing::exp::easeOut); + if (event.velocitySmoothed.y < 0) { + velocityModifier.y *= -1; + } + + ofVec2f newPos = scrollableArea->getPosition() + velocityModifier; + + newPos.x = ofClamp(newPos.x, getWidth() - scrollableArea->getWidth(), 0); + newPos.y = ofClamp(newPos.y, getHeight() - scrollableArea->getHeight(), 0); + + currentVelocity = velocityModifier; + isAutoScroll = true; + + } + + void ScrollableContainer::onTouchMove(TouchEvent & event) { + + // calculate movement frome last event + ofVec3f d = event.position - event.prevPosition; + ofVec3f newPos = scrollableArea->getPosition() + d; + + // clamp the position + newPos.x = ofClamp(newPos.x, getWidth() - scrollableArea->getWidth(),0); + newPos.y = ofClamp(newPos.y, getHeight() - scrollableArea->getHeight(),0); + + scrollableArea->setPosition(newPos); + } + + void ScrollableContainer::onScrollAreaSizeChanged(ofxInterface::Node & n) { + dragArea->setSize(n.getSize()); + } + + ///\brief moves child to scrollable Area + void ScrollableContainer::onChildAdded(ofxInterface::Node & n) { + + + // move child to scroll and resize continer + containerRemoveChild(&n); + scrollableArea->addChild(&n); + fitSizeToItems(); + + // clip render + n.setRenderClip(true); + + // add listeners + updateChildListeners(&n); + + + } + + void ScrollableContainer::onChildRemoved(ofxInterface::Node& n) + { + fitSizeToItems(); + } + + void ScrollableContainer::fitSizeToItems() + { + int w = baseSize.x; + int h = baseSize.y; + + for (auto& c : getChildWithName("scrollableArea")->getChildren()) + { + updateChildItemSize(c, w, h); + } + getChildWithName("scrollableArea")->setSize(w, h); + + } + + Node * ScrollableContainer::containerRemoveChild(Node * child, bool bMaintainChildGlobalTransform) + { + for (int i = 0; i < childNodes.size(); i++) + { + if (childNodes[i] == child) { + return containerRemoveChild(i, bMaintainChildGlobalTransform); + } + } + + ofLogWarning("ofxInterface::Node::removeChild", "are you trying to remove a child that does not exist?"); + return NULL; + } + + Node * ScrollableContainer::containerRemoveChild(int index, bool bMaintainChildGlobalTransform) + { + if (index >= childNodes.size()) { + ofLogWarning("ofxInterface::Node::removeChild", "are you trying to remove a child that does not exist?"); + return NULL; + } + + Node *child = childNodes[index]; + childNodes.erase(childNodes.begin() + index); + child->clearParent(bMaintainChildGlobalTransform); + //ofNotifyEvent(eventChildRemoved, *child, this); + return child; + } + + void ScrollableContainer::updateChildItemSize(Node* child, int& w, int& h) + { + for (auto& c : child->getChildren()) + { + updateChildItemSize(c, w, h); + } + + if (child->getPosition().x + child->getWidth() > w) { + w = child->getPosition().x + child->getWidth(); + } + if (child->getPosition().y + child->getHeight() > h) { + h = child->getPosition().y + child->getHeight(); + } + } + + void ScrollableContainer::updateChildListeners(Node* child) + { + for (auto& c : child->getChildren()) { + updateChildListeners(c); + } + + ofAddListener(child->eventTouchMove, this, &ScrollableContainer::onTouchMove); + ofAddListener(child->eventTouchUp, this, &ScrollableContainer::onTouchUp); + ofAddListener(child->eventTouchDown, this, &ScrollableContainer::onTouchDown); + } + + void ScrollableContainer::updateSlider() + { + // draw slider + float wSlider = 6; + float marginSlider = 20; + + if (scrollableArea->getWidth() > getWidth()) { + slider["wInactive"]->setVisible(true); + slider["w"]->setVisible(true); + + ofVec2f d0 = ofVec2f(marginSlider, getHeight() - marginSlider - wSlider); + //ofSetColor(scrollInactiveColor); + float width = getWidth() - 3 * marginSlider; + + slider["wInactive"]->setPosition(d0); + slider["wInactive"]->setSize(width, wSlider); + //ofDrawRectangle(d0, width, wSlider); + + //ofSetColor(scrollActiveColor); + float wActive = width * getWidth() / scrollableArea->getWidth(); + if (edgeAnimation.isAnimating()) { + wActive -= (1.0 - edgeAnimation.getPercentDone()) * 25; + } + float pos = (width - wActive) - ofMap(scrollableArea->getPosition().x, getWidth() - scrollableArea->getWidth(), 0, 0, width - wActive, true); + //ofDrawRectangle(d0 + ofVec2f(pos,0), wActive, wSlider); + slider["w"]->setPosition(d0 + ofVec2f(pos, 0)); + slider["w"]->setSize(wActive, wSlider); + } + else { + slider["wInactive"]->setVisible(false); + slider["w"]->setVisible(false); + } + + + if (scrollableArea->getHeight() > getHeight()) { + slider["hInactive"]->setVisible(true); + slider["h"]->setVisible(true); + + ofVec2f d0 = ofVec2f(getWidth() - marginSlider - wSlider, marginSlider); + //ofSetColor(scrollInactiveColor); + float height = getHeight() - 2 * marginSlider; + //ofDrawRectangle(d0, wSlider, height); + slider["hInactive"]->setPosition(d0); + slider["hInactive"]->setSize(wSlider, height); + + //ofSetColor(scrollActiveColor); + float hActive = height * getHeight() / scrollableArea->getHeight(); + if (edgeAnimation.isAnimating()) { + hActive -= (1.0 - edgeAnimation.getPercentDone()) * 25; + } + float pos = (height - hActive) - ofMap(scrollableArea->getPosition().y, getHeight() - scrollableArea->getHeight(), 0, 0, height - hActive, true); + //ofDrawRectangle(d0 +ofVec2f(0,pos), wSlider, hActive); + slider["h"]->setPosition(d0 + ofVec2f(0, pos)); + slider["h"]->setSize(wSlider, hActive); + } + else { + slider["hInactive"]->setVisible(false); + slider["h"]->setVisible(false); + } + } + + +} \ No newline at end of file diff --git a/src/components/ScrollableContainer.h b/src/components/ScrollableContainer.h new file mode 100644 index 0000000..45e36ac --- /dev/null +++ b/src/components/ScrollableContainer.h @@ -0,0 +1,82 @@ +#pragma once +#include "Node.h" +#include "ofxFontStash2.h" +#include "ofxAnimatableOfPoint.h" +#include "ofxEasing.h" +#include "ColorPanel.h" + +namespace ofxInterface { + + struct ScrollableContainerSettings : NodeSettings{ + ofVec2f sizeScrollableArea; + float plane = 0; // option to set scroll option above elements + + ofColor bgColor = ofColor(0, 0); + ofColor scrollActiveColor = ofColor(255); + ofColor scrollInactiveColor = ofColor(128); + + }; + + class ScrollableContainer : + public Node + { + public: + ScrollableContainer(); + ~ScrollableContainer(); + + virtual void setup(ScrollableContainerSettings s); + + virtual void draw() override; + + Node* getScrollableArea(); + //void addChildToScrollableArea(Node* child); + + virtual void onTouchDown(TouchEvent & event); + virtual void onTouchUp(TouchEvent & event); + virtual void onTouchMove(TouchEvent & event); + + void onScrollAreaSizeChanged(ofxInterface::Node & n); + + void onChildAdded(ofxInterface::Node& n); + void onChildRemoved(ofxInterface::Node& n); + void fitSizeToItems(); + + protected: + Node* containerRemoveChild(Node *child, bool bMaintainChildGlobalTransform = false); + Node* containerRemoveChild(int index, bool bMaintainChildGlobalTransform = false); + void updateChildItemSize(Node* child, int& w, int& h); + void updateChildListeners(Node* child); + + void updateSlider(); + + private: + Node* scrollableArea; + Node* dragArea; + map slider; + ofVec2f pStartDragCursor; + ofVec2f pStartDrag; + ofVec2f baseSize; + + ofColor bgColor; + ofColor scrollActiveColor; + ofColor scrollInactiveColor; + + /// \brief rollout animation on touch up + ofxAnimatableOfPoint edgeAnimation; + ofVec2f currentVelocity; + ofVec2f finalPoint; + bool animationReady = false; + const float friction = 0.94; + bool isAutoScroll = false; + + /// \brief clamp of velocity animation sliding + const int maxVelocity = 1000; + + /// \brief in s + const float maxAnimationLength = 2.3f; + + + + }; +} + diff --git a/src/components/SimpleChecker.cpp b/src/components/SimpleChecker.cpp new file mode 100644 index 0000000..71fac9a --- /dev/null +++ b/src/components/SimpleChecker.cpp @@ -0,0 +1,60 @@ +#include "SimpleChecker.h" + + +namespace ofxInterface { + SimpleChecker::SimpleChecker() + { + } + + + SimpleChecker::~SimpleChecker() + { + } + + void SimpleChecker::setup(ModalElementSettings s) + { + s.type = CHECKER; + ModalElement::setup(s); + + colorFade.setColor(colorInactive); + colorFade.setDuration(0.10); + + // init shape + float multX = float(getWidth()) / 32; + float multY = float(getHeight()) / 32; + checkerShape.moveTo(2*multX, 6 * multY); + checkerShape.lineTo(0 * multX, 8 * multY); + checkerShape.lineTo(7 * multX, 15 * multY); + checkerShape.lineTo(20 * multX, 2 * multY); + checkerShape.lineTo(18 * multX, 0 * multY); + checkerShape.lineTo(7 * multX, 11 * multY); + checkerShape.lineTo(2 * multX,6 * multY); + + checkerShape.close(); + checkerShape.translate(ofVec2f(6 * multX, 8 * multY)); + checkerShape.setColor(colorSelected); + } + void SimpleChecker::draw() + { + colorFade.update(ofGetLastFrameTime()); + ofSetColor(colorFade.getCurrentColor()); + + ofDrawRectangle(0, 0, getWidth(), getHeight()); + + if (isSelected) { + checkerShape.draw(); + } + + } + + void SimpleChecker::onStateChanged(bool & isActive) + { + if (isActive) { + colorFade.animateTo(colorActive); + } + else { + colorFade.animateTo(colorInactive); + } + } + +} \ No newline at end of file diff --git a/src/components/SimpleChecker.h b/src/components/SimpleChecker.h new file mode 100644 index 0000000..7732a01 --- /dev/null +++ b/src/components/SimpleChecker.h @@ -0,0 +1,26 @@ +#pragma once +#include "ModalElement.h" +#include "ofxAnimatable.h" +#include "ofxAnimatableOfColor.h" + +namespace ofxInterface { + + class SimpleChecker : public ModalElement + { + public: + SimpleChecker(); + ~SimpleChecker(); + + void setup(ModalElementSettings s); + virtual void draw(); + + void onStateChanged(bool& isActive); + + protected: + ofxAnimatableOfColor colorFade; + int currentAni = 0; + + ofPath checkerShape; + + }; +} diff --git a/src/components/Slider.cpp b/src/components/Slider.cpp new file mode 100644 index 0000000..da63750 --- /dev/null +++ b/src/components/Slider.cpp @@ -0,0 +1,110 @@ +#include "Slider.h" + + +namespace ofxInterface { + Slider::Slider() + { + ofAddListener(eventTouchMove, this, &Slider::onTouchMove, 100); + ofAddListener(eventTouchDown, this, &Slider::onTouchDown, 100); + //ofAddListener(eventTouchUp, this, &Slider::onTouchUp, 100); + value.set("value", 0, 0, 1); + } + + Slider::Slider(const Slider & mom) : Node(mom) + { + colorActive = mom.colorActive; + colorSelected = mom.colorSelected; + lineWidth = mom.lineWidth; + colorInactive = mom.colorInactive; + direction = mom.direction; + value = mom.value; + ofAddListener(eventTouchMove, this, &Slider::onTouchMove, 100); + ofAddListener(eventTouchDown, this, &Slider::onTouchDown, 100); + } + + + Slider::~Slider() + { + } + + Node * Slider::clone() + { + return new Slider(*this); + } + + void Slider::setup(SliderSettings s) + { + Node::setup(s); + colorActive = s.colorActive; + colorSelected = s.colorSelected; + lineWidth = s.lineWidth; + colorInactive = s.colorInactive; + direction = s.direction; + } + + void Slider::draw() + { + float lActive; + float lInactive; + + ofRectangle rActive; + ofRectangle rInactive; + + if (direction == HORIZONTAL) { + lActive = ofMap(value, value.getMin(), value.getMax(), 0, getWidth(), true); + lInactive = getWidth() - lActive; + rActive = ofRectangle(0, 0.5*(getHeight() - lineWidth), lActive, lineWidth); + rInactive = ofRectangle(lActive, 0.5*(getHeight() - lineWidth), lInactive, lineWidth); + }else { + lActive = ofMap(value, value.getMin(), value.getMax(), 0, getHeight(), true); + lInactive = getHeight() - lActive; + rActive = ofRectangle(0.5*(getWidth() - lineWidth),0, lineWidth, lActive ); + rInactive = ofRectangle(0.5*(getWidth() - lineWidth),lActive, lineWidth,lInactive); + } + + ofSetColor(colorSelected); + ofDrawRectangle(rActive); + ofSetColor(colorInactive); + ofDrawRectangle(rInactive); + ofSetColor(colorActive); + ofDrawCircle(rInactive.x, rInactive.y, 0.5*getHeight()); + } + + void Slider::onTouchDown(TouchEvent & e) { + if (direction == HORIZONTAL) { + value = ofMap(toLocal(e.position).x, 0, getWidth(), value.getMin(), value.getMax(), true); + } + else { + value = ofMap(toLocal(e.position).y, 0, getHeight(), value.getMin(), value.getMax(), true); + } + + } + + void Slider::onTouchMove(TouchEvent & e) + { + if (direction == HORIZONTAL) { + value = ofMap(toLocal(e.position).x, 0, getWidth(), value.getMin(), value.getMax(), true); + } + else { + value = ofMap(toLocal(e.position).y, 0, getHeight(), value.getMin(), value.getMax(), true); + } + } + + void Slider::setDirection(Direction type) + { + direction = type; + } + + Direction Slider::getDirection() + { + return direction; + } + + /*void Slider::onTouchUp(TouchEvent & e) { + if (type == BUTTON) { + isSelected = false; + } + }*/ + + +} \ No newline at end of file diff --git a/src/components/Slider.h b/src/components/Slider.h new file mode 100644 index 0000000..aba5e59 --- /dev/null +++ b/src/components/Slider.h @@ -0,0 +1,47 @@ +#pragma once +#include "Node.h" + +namespace ofxInterface { + enum Direction { + HORIZONTAL, + VERTICAL + }; + + struct SliderSettings : NodeSettings{ + Direction direction = HORIZONTAL; + ofColor colorActive = ofColor(200); + ofColor colorSelected = ofColor(100); + ofColor colorInactive = ofColor(50); + int lineWidth = 6; + }; + + class Slider : public Node + { + public: + Slider(); + Slider(const Slider& mom); + ~Slider(); + + virtual Node* clone() override; + + void setup(SliderSettings s); + virtual void draw(); + + void onTouchDown(TouchEvent& e); + //void onTouchUp(TouchEvent& e); + void onTouchMove(TouchEvent& e); + + void setDirection(Direction type); + Direction getDirection(); + + ofColor colorActive; + ofColor colorSelected; + ofColor colorInactive; + + ofParameter value; + + private: + Direction direction; + int lineWidth; + }; +} diff --git a/src/components/SoftKeyboard.cpp b/src/components/SoftKeyboard.cpp new file mode 100644 index 0000000..58321b0 --- /dev/null +++ b/src/components/SoftKeyboard.cpp @@ -0,0 +1,305 @@ +/* + * SoftKeyboard.cpp + * emptyExample + * + * Created by Brian Eschrich on 23.02.16 + * Copyright 2017 reddo UG. All rights reserved. + * + */ + +#include "SoftKeyboard.h" + +namespace ofxInterface { + void SoftKeyboard::setup(SoftKeyboardSettings s) + { + Node::setup(s); + font = s.font; + style = s.style; + + bgColor = s.bgColor; + colorActive = s.colorActive; + colorInactive = s.colorInactive; + colorSelected = s.colorSelected; + borderRadius = s.borderRadius; + borderWidth = s.borderWidth; + padding = s.padding; + margin = s.margin; + textureKeys = s.textureKeys; + + + + // fixed ratio + if (getHeight() == 0) { + setHeight(getWidth() * 19.4 / 51); + } + + + // create keys, different layout for code and language input + if (s.layout == "code") { + wKey = (getWidth() - 2 * (margin + borderWidth) - 9 * padding) / 10; + } + else { + wKey = (getWidth() - 2 * (margin + borderWidth) - 10 * padding) / 11; + } + hKey = (getHeight() - 2 * (margin + borderWidth) - 3 * padding) / 4; + + auto keyLayout = getKeyLayout(s.layout); + for (auto& l:keyLayout) + { + addSet(l.first, l.second); + } + + setActiveKeyset( "alpha"); + + + // draw border + if (borderWidth > 0) { + borderPath.setColor(style.color); + borderPath.setArcResolution(200); + // other path + borderPath.moveTo(borderRadius, 0); + borderPath.lineTo(getWidth() - borderRadius, 0); + borderPath.arc(ofVec2f(getWidth() - borderRadius, borderRadius), borderRadius, borderRadius, 270, 0); + borderPath.lineTo(getWidth(), getHeight() - borderRadius); + borderPath.arc(ofVec2f(getWidth() - borderRadius, getHeight() - borderRadius), borderRadius, borderRadius, 0, 90); + borderPath.lineTo(borderRadius, getHeight()); + borderPath.arc(ofVec2f(borderRadius, getHeight() - borderRadius), borderRadius, borderRadius, 90, 180); + borderPath.lineTo(0, borderRadius); + borderPath.arc(ofVec2f(borderRadius, borderRadius), borderRadius, borderRadius, 180, 270); + // inner path + borderPath.moveTo(borderRadius, borderWidth); + borderPath.lineTo(getWidth() - borderRadius, borderWidth); + borderPath.arc(ofVec2f(getWidth() - borderRadius, borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 270, 0); + borderPath.lineTo(getWidth() - borderWidth, getHeight() - borderRadius); + borderPath.arc(ofVec2f(getWidth() - borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 0, 90); + borderPath.lineTo(borderRadius, getHeight() - borderWidth); + borderPath.arc(ofVec2f(borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 90, 180); + borderPath.lineTo(borderWidth, borderRadius); + borderPath.arc(ofVec2f(borderRadius, borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 180, 270); + borderPath.close(); + } + } + + //------------------------------------------------------------------ + void SoftKeyboard::draw() { + borderPath.draw(); + + } + + void SoftKeyboard::setActiveKeyset(string id) + { + for (auto& keySet : keySets) { + if (keySet.first == id) { + keySet.second->activate(); + activeKeySet = keySet.second; + } + else { + keySet.second->deactivate(); + } + } + } + + void SoftKeyboard::addSet(string keySet, vector> keys) + { + keySets.insert(make_pair(keySet, new Node())); + addChild(keySets[keySet]); + keySets[keySet]->setName(keySet); + + int py = borderWidth + margin; + for (auto& line : keys) { + addLine(keySets[keySet], line, py); + py += hKey + padding; + } + } + + void SoftKeyboard::addLine(Node* keySet, vector keys, vector widthKeys, int y) + { + int wLine = margin* (widthKeys.size()-1); + for (auto& w : widthKeys) { + wLine += wKey * w; + } + + float x = margin + borderWidth; + int i = 0; + for (auto& key : keys) { + float w = widthKeys[i]; + if (key != 10000) { + addKey(keySet, key, x, y, w, hKey); + } + x += w + padding; + ++i; + } + } + + void SoftKeyboard::addLine(Node* keySet, vector keys, int y){ + map keyWidths; + keyWidths[' '] = wKey * 5 + padding * 4; + keyWidths['@'] = (3 * wKey + padding) *0.5; + keyWidths[9997] = (3 * wKey + padding) *0.5; // abc + keyWidths[9996] = (3 * wKey + padding) *0.5; // 123 + keyWidths[3680] = wKey * 2 + padding; // shift + keyWidths[8] = wKey * 2 + padding; // backspace + keyWidths[13] = wKey * 2 + padding; // return + keyWidths[10000] = (wKey ) * 0.5; // half key empty space + + vector widthKeys; + + for (auto key : keys) { + if (keyWidths.find(key) != keyWidths.end()) { + widthKeys.push_back(keyWidths[key]); + }else { + widthKeys.push_back(wKey); + } + } + addLine(keySet, keys, widthKeys, y); + } + + void SoftKeyboard::addKey(Node* keySet, int keyValue, int x, int y, int w, int h){ + + SoftKeyboardKey* key = new SoftKeyboardKey(); + SoftKeyboardKeySettings s; + + s.name = "key_" + ofToString(keyValue); + s.position = ofVec2f(x, y); + s.size = ofVec2f(w, h); + s.borderWidth = borderWidth * 0.25; + s.borderRadius = borderRadius * 0.5; + s.colorActive = colorActive; + s.colorInactive = colorInactive; + s.colorSelected = colorSelected; + s.font = font; + s.key = keyValue; + s.style = style; + + if (textureKeys.find(keyValue) != textureKeys.end()) { + s.isTextureKey = true; + s.keyTexture = textureKeys[keyValue]; + } + + key->setup(s); + + keySet->addChild(key); + + ofAddListener(key->keyPressed, this, &SoftKeyboard::onKeyPressed); + ofAddListener(key->keyReleased, this, &SoftKeyboard::onKeyReleased); + } + + map>> SoftKeyboard::getKeyLayout(string layout) + { + map>> ret; + vector alphaKeys; + vector alphaBigKeys; + vector numbers; + + if (layout == "de") { + vector< vector> alphaKeys = { + {'q','w','e','r','t','z','u','i','o','p',252}, + {'a','s','d','f','g','h','j','k','l',246,228}, + { 3680,'y','x','c','v','b','n','m',8}, + { 9997,'@',' ','.',13} + }; + + ret.insert(make_pair("alpha", alphaKeys)); + + vector> alphaBigKeys = { + {'Q','W','E','R','T','Z','U','I','O','P',220}, + {'A','S','D','F','G','H','J','K','L',214,196}, + { 3680,'Y','X','C','V','B','N','M',8}, + { 9997,'@',' ','.',13} + }; + + ret.insert(make_pair("alphaBig", alphaBigKeys)); + + vector< vector> numbers = { + {'1','2','3','4','5','6','7','8','9','0','='}, + {'!','#','$','%','&','\'','*','+','-','/','?'}, + {'_',223,'|','{','}','[',']','(',')',',',':'}, + { 9996,'@',' ',';',13} + }; + + ret.insert(make_pair("numbers", numbers)); + } + else if(layout == "en") { + vector< vector> alphaKeys = { + {'q','w','e','r','t','y','u','i','o','p',252}, + {'a','s','d','f','g','h','j','k','l',246,228}, + { 3680,'z','x','c','v','b','n','m',8}, + { 9997,'@',' ','.',13} + }; + + ret.insert(make_pair("alpha", alphaKeys)); + + vector> alphaBigKeys = { + {'Q','W','E','R','T','Y','U','I','O','P',220}, + {'A','S','D','F','G','H','J','K','L',214,196}, + { 3680,'Z','X','C','V','B','N','M',8}, + { 9997,'@',' ','.',13} + }; + + ret.insert(make_pair("alphaBig", alphaBigKeys)); + + vector< vector> numbers = { + {'1','2','3','4','5','6','7','8','9','0','='}, + {'!','#','$','%','&','\'','*','+','-','/','?'}, + {'_',223,'|','{','}','[',']','(',')',',',':'}, + { 9996,'@',' ',';',13} + }; + + ret.insert(make_pair("numbers", numbers)); + } + else if (layout == "code") { + vector< vector> alphaKeys = { + {'1','2','3','4','5','6','7','8','9','0'}, + {'Q','W','E','R','T','Y','U','I','O','P'}, + {10000,'A','S','D','F','G','H','J','K','L'}, + {10000,'Z','X','C','V','B','N','M',8} + }; + + ret.insert(make_pair("alpha", alphaKeys)); + + vector> alphaBigKeys = { + {'1','2','3','4','5','6','7','8','9','0'}, + {'Q','W','E','R','T','Y','U','I','O','P'}, + {10000,'A','S','D','F','G','H','J','K','L'}, + {10000,'Z','X','C','V','B','N','M',8} + }; + + ret.insert(make_pair("alphaBig", alphaBigKeys)); + + vector< vector> numbers = { + {'1','2','3','4','5','6','7','8','9','0'}, + {'Q','W','E','R','T','Y','U','I','O','P'}, + {10000,'A','S','D','F','G','H','J','K','L'}, + {10000,'Z','X','C','V','B','N','M',8} + }; + + ret.insert(make_pair("numbers", numbers)); + } + + return ret; + + } + + void SoftKeyboard::onKeyPressed(ofKeyEventArgs &event) + { + if (activeKeySet->getName() == "alphaBig") { + setActiveKeyset("alpha"); + } + else if(activeKeySet->getName() == "alpha" && (event.key == 3680 || event.key == 3681)){ + setActiveKeyset("alphaBig"); + } + else if (event.key == 9996) { + setActiveKeyset("alpha"); + } + else if (event.key == 9997) { + setActiveKeyset("numbers"); + } + ofNotifyEvent(keyPressed, event); + } + + void SoftKeyboard::onKeyReleased(ofKeyEventArgs &event) + { + ofNotifyEvent(keyReleased, event); + } + +} \ No newline at end of file diff --git a/src/components/SoftKeyboard.h b/src/components/SoftKeyboard.h new file mode 100644 index 0000000..da323e7 --- /dev/null +++ b/src/components/SoftKeyboard.h @@ -0,0 +1,88 @@ +/* + * SoftKeyboard.h + * emptyExample + * + * Created by Brian Eschrich on 23.02.16 + * Copyright 2017 reddo UG. All rights reserved. + * + */ + +#ifndef _SoftKeyboard +#define _SoftKeyboard + +#include "ofMain.h" +#include "SoftKeyboardKey.h" + +namespace ofxInterface { + struct SoftKeyboardSettings : NodeSettings { + shared_ptr font; + ofxFontStash2::Style style; + + ofColor bgColor = ofColor(0, 0); + ofColor colorActive = ofColor(255); + ofColor colorInactive = ofColor(128); + ofColor colorSelected = ofColor(128); + + int borderRadius = 8; + int borderWidth = 4; + int margin = 12; + int padding = 8; + + string layout = "de"; + + map textureKeys; + + }; + + +class SoftKeyboard : public Node{ + +public: + virtual void setup(SoftKeyboardSettings s); + virtual void draw(); + + void setActiveKeyset(string id); + + void onKeyPressed(ofKeyEventArgs& event); + void onKeyReleased(ofKeyEventArgs& event); + + ofEvent keyPressed; + ofEvent keyReleased; + +protected: + void addSet(string keySet, vector> keys); + void addLine(Node* keySet, vector keys, vector widthKeys, int y); + void addLine(Node* keySet, vector keys, int y); + void addKey(Node* keySet, int keyValue, int x, int y, int w, int h); + + map> > getKeyLayout(string layout); + + map keySets; + Node* activeKeySet; + + shared_ptr font; + ofxFontStash2::Style style; + + ofColor bgColor; + ofColor colorActive; + ofColor colorInactive; + ofColor colorSelected; + int borderRadius; + + int wKey; + int hKey; + int margin; + int padding; + int borderWidth; + + ofPath borderPath; + map textureKeys; + +private: + + + +}; +} + +#endif diff --git a/src/components/SoftKeyboardKey.cpp b/src/components/SoftKeyboardKey.cpp new file mode 100644 index 0000000..7b6ec25 --- /dev/null +++ b/src/components/SoftKeyboardKey.cpp @@ -0,0 +1,295 @@ +/* + * Key.cpp + * emptyExample + * + * Created by Brian Eschrich on 23.02.16 + * Copyright 2017 reddo UG. All rights reserved. + * + */ + +#include "SoftKeyboardKey.h" + +namespace ofxInterface { + void SoftKeyboardKey::setup(SoftKeyboardKeySettings s) + { + // init values + ModalElement::setup(s); + font = s.font; + style = s.style; + key = s.key; + borderRadius = s.borderRadius; + borderWidth = s.borderWidth; + isTextureKey = s.isTextureKey; + keyTexture = s.keyTexture; + + + // draw paths + if (borderWidth > 0) { + borderPath.setColor(style.color); + borderPath.setArcResolution(200); + // other path + borderPath.moveTo(borderRadius, 0); + borderPath.lineTo(getWidth() - borderRadius, 0); + borderPath.arc(ofVec2f(getWidth() - borderRadius, borderRadius), borderRadius, borderRadius, 270, 0); + borderPath.lineTo(getWidth(),getHeight()-borderRadius); + borderPath.arc(ofVec2f(getWidth() - borderRadius, getHeight() - borderRadius), borderRadius, borderRadius, 0, 90); + borderPath.lineTo(borderRadius, getHeight()); + borderPath.arc(ofVec2f(borderRadius, getHeight() - borderRadius), borderRadius, borderRadius, 90, 180); + borderPath.lineTo(0, borderRadius); + borderPath.arc(ofVec2f(borderRadius, borderRadius), borderRadius, borderRadius, 180, 270); + // inner path + borderPath.moveTo(borderRadius, borderWidth); + borderPath.lineTo(getWidth() - borderRadius, borderWidth); + borderPath.arc(ofVec2f(getWidth() - borderRadius, borderRadius), borderRadius - borderWidth, borderRadius -borderWidth, 270, 0); + borderPath.lineTo(getWidth() - borderWidth, getHeight() - borderRadius); + borderPath.arc(ofVec2f(getWidth() - borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 0, 90); + borderPath.lineTo(borderRadius, getHeight() - borderWidth); + borderPath.arc(ofVec2f(borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 90, 180); + borderPath.lineTo(borderWidth, borderRadius); + borderPath.arc(ofVec2f(borderRadius, borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 180, 270); + borderPath.close(); + } + + // background + if (!(colorInactive.a == 0 && colorActive.a == 0 && colorSelected.a == 0)) { + fillPath.setUseShapeColor(false); + fillPath.setArcResolution(200); + fillPath.moveTo(borderRadius, borderWidth); + fillPath.lineTo(getWidth() - borderRadius, borderWidth); + fillPath.arc(ofVec2f(getWidth() - borderRadius, borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 270, 0); + fillPath.lineTo(getWidth() - borderWidth, getHeight() - borderRadius); + fillPath.arc(ofVec2f(getWidth() - borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 0, 90); + fillPath.lineTo(borderRadius, getHeight() - borderWidth); + fillPath.arc(ofVec2f(borderRadius, getHeight() - borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 90, 180); + fillPath.lineTo(borderWidth, borderRadius); + fillPath.arc(ofVec2f(borderRadius, borderRadius), borderRadius - borderWidth, borderRadius - borderWidth, 180, 270); + fillPath.close(); + } + + generateText(); + + // listener + ofAddListener(eventTouchDown, this, &SoftKeyboardKey::onTouchDown); + ofAddListener(eventTouchUp, this, &SoftKeyboardKey::onTouchUp); + } + + //------------------------------------------------------------------ + void SoftKeyboardKey::draw() { + + if (!isTouched() && isSelected) { + isSelected = false; + } + + borderPath.draw(); + + if (isSelected) { + ofSetColor(colorSelected); + } + else if(getEnabled()){ + ofSetColor(colorActive); + } + else { + ofSetColor(colorInactive); + } + + fillPath.draw(); + + if (!isTextureKey) { + //center text + ofRectangle r = font->getTextBounds(text, style, 0, 0); + // hack to reduce line height to glyph height + r.y += r.height * 0.25; + r.height *= 0.6; + + font->draw(text, style, (getWidth() - r.width) / 2, 0.5 * (getHeight() - r.height) - r.y); + specialKeyPath.draw(specialKeyPos.x, specialKeyPos.y); + } + else { + ofSetColor(style.color); + keyTexture.draw(0, 0, getWidth(), getHeight()); + } + + + } + + + + void SoftKeyboardKey::onTouchDown(TouchEvent &event) + { + + ofKeyEventArgs out(ofKeyEventArgs::Pressed, key); + + ofNotifyEvent(keyPressed, out); + + + bTouched = true; + } + + void SoftKeyboardKey::onTouchUp(TouchEvent &event) + { + ofKeyEventArgs out(ofKeyEventArgs::Released, key); + ofNotifyEvent(keyReleased, out); + + bTouched = false; + } + void SoftKeyboardKey::generateText() + { + + float glyphHeight = font->getTextBounds("X", style, 0, 0).height*0.6; + float glyphCurveRadius = borderRadius * 0.5; + float glyphStroke = borderWidth * 2; + + if (key == 252) { + text = "ü"; + } + else if (key == 220) { + text = "Ü"; + } + else if (key == 228) { + text = "ä"; + } + else if (key == 196) { + text = "Ä"; + } + else if (key == 246) { + text = "ö"; + } + else if (key == 214) { + text = "Ö"; + } + else if (key == 223) { + text = "ß"; + } + else if (key == 8364) { + text = "€"; + } + else if (key == 127) { + text = "b"; + } + else if (key == 13) { + text = "Return"; + } + else if (key == 8) { // backspace + + text = ""; + specialKeyPath.setColor(style.color); + specialKeyPath.setArcResolution(50); + // outher path + specialKeyPath.moveTo(glyphHeight *24.0/18.0 - borderRadius, 0); + specialKeyPath.arc(ofVec2f(glyphHeight *24.0 / 18.0 - borderRadius, borderRadius), borderRadius, borderRadius, 270, 0); // rightTop + specialKeyPath.lineTo(glyphHeight *24.0 / 18.0, glyphHeight - borderRadius); + specialKeyPath.arc(ofVec2f(glyphHeight *24.0 / 18.0 - borderRadius, glyphHeight - borderRadius), borderRadius, borderRadius, 0, 90); // rightBottom + specialKeyPath.lineTo(glyphHeight *9.0 / 18.0, glyphHeight); + specialKeyPath.arc(ofVec2f(glyphHeight *9.0 / 18.0 - glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 90, 135); // leftBottom + specialKeyPath.lineTo(glyphCurveRadius * 0.5, 9.0/18.0*glyphHeight + glyphCurveRadius); + specialKeyPath.arc(ofVec2f(glyphCurveRadius, 9.0 / 18.0*glyphHeight), glyphCurveRadius, glyphCurveRadius, 135, 225); // left + specialKeyPath.lineTo(9.0 / 18.0*glyphHeight - 1.924*glyphCurveRadius, glyphCurveRadius * 0.383); + specialKeyPath.arc(ofVec2f(9.0 / 18.0*glyphHeight - glyphCurveRadius, glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 225, 270); // leftTop + specialKeyPath.close(); + + // inner path + specialKeyPath.moveTo(glyphHeight *24.0 / 18.0 - glyphCurveRadius, glyphStroke); + specialKeyPath.arc(ofVec2f(glyphHeight *24.0 / 18.0 - glyphCurveRadius, glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 270, 0); // rightTop + specialKeyPath.arc(ofVec2f(glyphHeight *24.0 / 18.0 - glyphCurveRadius, glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 270, 0); // rightTop + specialKeyPath.lineTo(glyphHeight *24.0 / 18.0 - glyphStroke, glyphHeight - glyphCurveRadius); + specialKeyPath.arc(ofVec2f(glyphHeight *24.0 / 18.0 - glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 0, 90); // rightBottom + specialKeyPath.lineTo(glyphHeight *9.0 / 18.0, glyphHeight - glyphStroke); + specialKeyPath.arc(ofVec2f(glyphHeight *9.0 / 18.0 - glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 90, 135); // leftBottom + specialKeyPath.lineTo(glyphCurveRadius * 0.5 + glyphStroke*0.924, 9.0 / 18.0*glyphHeight + glyphCurveRadius); + specialKeyPath.arc(ofVec2f(glyphCurveRadius, 9.0 / 18.0*glyphHeight), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 135, 225); // left + specialKeyPath.lineTo(9.0 / 18.0 * glyphHeight - 1.8 * glyphCurveRadius, glyphCurveRadius * 0.383 + glyphStroke); + specialKeyPath.arc(ofVec2f(9.0 / 18.0*glyphHeight - glyphCurveRadius, glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 225, 270); // leftTop + specialKeyPath.close(); + + // x + int glyphStrokeX = glyphStroke * 0.5; + float glyphStrokeD = sqrt(glyphStrokeX*glyphStrokeX * 2); + specialKeyPath.moveTo(glyphHeight *15.0 / 18.0 - glyphStrokeD, glyphHeight *9.0 / 18.0); + specialKeyPath.lineTo(glyphHeight *12.0 / 18.0 - glyphStrokeX * 0.383, glyphHeight *12.0 / 18.0 -glyphStrokeX * 0.924); + specialKeyPath.arcNegative(ofVec2f(glyphHeight *12.0 / 18.0, glyphHeight *12.0 / 18.0), glyphStrokeX, glyphStrokeX, 225, 45); // leftBottom + specialKeyPath.lineTo(glyphHeight *15.0 / 18.0, glyphHeight *9.0 / 18.0 + glyphStrokeD); + specialKeyPath.lineTo(glyphHeight *18.0 / 18.0 - glyphStrokeX * 0.383, glyphHeight *12.0 / 18.0 + glyphStrokeX * 0.924); + specialKeyPath.arcNegative(ofVec2f(glyphHeight *18.0 / 18.0, glyphHeight *12.0 / 18.0), glyphStrokeX, glyphStrokeX, 135, 315); // rightBottom + specialKeyPath.lineTo(glyphHeight *15.0 / 18.0 + glyphStrokeD, glyphHeight *9.0 / 18.0); + specialKeyPath.lineTo(glyphHeight *18.0 / 18.0 + glyphStrokeX * 0.383, glyphHeight *6.0 / 18.0 +glyphStrokeX * 0.924); + specialKeyPath.arcNegative(ofVec2f(glyphHeight *18.0 / 18.0, glyphHeight *6.0 / 18.0), glyphStrokeX, glyphStrokeX, 45, 225); // rightTop + specialKeyPath.lineTo(glyphHeight *15.0 / 18.0 , glyphHeight *9.0 / 18.0 - glyphStrokeD); + specialKeyPath.lineTo(glyphHeight *12.0 / 18.0 + glyphStrokeX * 0.383, glyphHeight *6.0 / 18.0 - glyphStrokeX * 0.924); + specialKeyPath.arcNegative(ofVec2f(glyphHeight *12.0 / 18.0 , glyphHeight *6.0 / 18.0), glyphStrokeX, glyphStrokeX, 315, 135); // leftTop + specialKeyPath.close(); + + float wGlyph = glyphHeight * 12 / 9.0; + specialKeyPos = ofVec2f(0.48*(getWidth() - wGlyph), 0.5*(getHeight() - glyphHeight)); + } + else if (key == 769) { + text = "strg"; + } + else if (key == 770) { + text = "strg"; + } + else if (key == 1281) { + text = "alt"; + } + else if (key == 1282) { + text = "alt"; + } + else if (key == 3680 || key == 3681) {//shift + text = ""; + + specialKeyPath.setColor(style.color); + specialKeyPath.setArcResolution(50); + // other path + specialKeyPath.moveTo(glyphCurveRadius, 11.0/18.0*glyphHeight); + specialKeyPath.lineTo(4.0 / 18.0*glyphHeight, 11.0 / 18.0*glyphHeight); + specialKeyPath.lineTo(4.0 / 18.0*glyphHeight, glyphHeight - glyphCurveRadius); + specialKeyPath.arcNegative(ofVec2f(4.0 / 18.0*glyphHeight + glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 180, 90); + specialKeyPath.lineTo(16.0 / 18.0*glyphHeight - glyphCurveRadius, glyphHeight); + specialKeyPath.arcNegative(ofVec2f(16.0 / 18.0*glyphHeight - glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 90, 0); + specialKeyPath.lineTo(16.0 / 18.0*glyphHeight, 11.0 / 18.0*glyphHeight); + specialKeyPath.lineTo(20.0 / 18.0*glyphHeight - glyphCurveRadius, 11.0 / 18.0*glyphHeight); + specialKeyPath.arcNegative(ofVec2f(20.0 / 18.0*glyphHeight - glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 90, 315); // right + specialKeyPath.lineTo(10.0 / 18.0*glyphHeight + glyphCurveRadius*1.5, glyphCurveRadius); + specialKeyPath.arcNegative(ofVec2f(10.0 / 18.0*glyphHeight, glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 315, 225); // top + specialKeyPath.lineTo((1.0 - 0.383)*glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphCurveRadius * 1.924); + specialKeyPath.arcNegative(ofVec2f(glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphCurveRadius), glyphCurveRadius, glyphCurveRadius, 225, 90); // left + + // inner path + specialKeyPath.moveTo(glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphStroke); + specialKeyPath.lineTo(4.0 / 18.0*glyphHeight + glyphStroke, 11.0 / 18.0*glyphHeight- glyphStroke); + specialKeyPath.lineTo(4.0 / 18.0*glyphHeight + glyphStroke, glyphHeight - glyphCurveRadius); + specialKeyPath.arcNegative(ofVec2f(4.0 / 18.0*glyphHeight + glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius- glyphStroke, glyphCurveRadius - glyphStroke, 180, 90); + specialKeyPath.lineTo(16.0 / 18.0*glyphHeight - glyphCurveRadius, glyphHeight - glyphStroke); + specialKeyPath.arcNegative(ofVec2f(16.0 / 18.0*glyphHeight - glyphCurveRadius, glyphHeight - glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 90, 0); + specialKeyPath.lineTo(16.0 / 18.0*glyphHeight - glyphStroke, 11.0 / 18.0*glyphHeight - glyphStroke); + specialKeyPath.lineTo(20.0 / 18.0*glyphHeight - glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphStroke); + specialKeyPath.arcNegative(ofVec2f(20.0 / 18.0*glyphHeight - glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 90, 315); // right + specialKeyPath.lineTo(10.0 / 18.0*glyphHeight + glyphCurveRadius * 1.5 - glyphStroke, glyphCurveRadius); + specialKeyPath.arcNegative(ofVec2f(10.0 / 18.0*glyphHeight, glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 315, 225); // top + specialKeyPath.lineTo((1.0 - 0.383)*glyphCurveRadius + glyphStroke, 11.0 / 18.0*glyphHeight - glyphCurveRadius * 1.924); + specialKeyPath.arcNegative(ofVec2f(glyphCurveRadius, 11.0 / 18.0*glyphHeight - glyphCurveRadius), glyphCurveRadius - glyphStroke, glyphCurveRadius - glyphStroke, 225, 90); // left + borderPath.close(); + + float wGlyph = glyphHeight * 10 / 9.0; + specialKeyPos = ofVec2f(0.5*(getWidth() - wGlyph), 0.5*(getHeight() - glyphHeight)); + + } + else if (key == 94) { //roof + text = "^"; + } + else if (key == 356) { //left + text = "<-"; + } + else if (key == 358) { //right + text = "->"; + } + else if (key == 9997) { //switch keypad + text = "123"; + } + else if (key == 9996) { //switch keys + text = "abc"; + } + else { + text += key; + } + } +} diff --git a/src/components/SoftKeyboardKey.h b/src/components/SoftKeyboardKey.h new file mode 100644 index 0000000..acbb3b4 --- /dev/null +++ b/src/components/SoftKeyboardKey.h @@ -0,0 +1,70 @@ +/* + * Key.h + * emptyExample + * + * Created by Brian Eschrich on 23.02.16 + * Copyright 2017 reddo UG. All rights reserved. + * + */ + +#ifndef _SoftKeyboardKey +#define _SoftKeyboardKey + +#include "ofMain.h" +#include "ModalElement.h" +#include "ofxFontStash2.h" + +namespace ofxInterface { + struct SoftKeyboardKeySettings : ModalElementSettings { + shared_ptr font; + ofxFontStash2::Style style; + + int key = ' '; + + int borderRadius = 4; + int borderWidth = 1; + + bool isTextureKey = false; + ofTexture keyTexture; + + }; + + class SoftKeyboardKey : public ModalElement { + + public: + virtual void setup(SoftKeyboardKeySettings s); + virtual void draw(); + + void onTouchDown(TouchEvent& event); + void onTouchUp(TouchEvent& event); + + ofEvent keyPressed; + ofEvent keyReleased; + + protected: + void generateText(); + + private: + bool bTouched; + + int key; + + int borderRadius; + int borderWidth; + + shared_ptr font; + ofxFontStash2::Style style; + + ofPath borderPath; + ofPath fillPath; + ofPath specialKeyPath; + ofVec2f specialKeyPos; + + string text = ""; + + bool isTextureKey = false; + ofTexture keyTexture; + }; +} + +#endif diff --git a/src/components/SolidColorPanel.cpp b/src/components/SolidColorPanel.cpp deleted file mode 100644 index 6d4db0f..0000000 --- a/src/components/SolidColorPanel.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// SolidColorPanel.cpp -// ofxInterface -// -// Created by Gal Sasson on 8/13/14. -// -// - -#include "SolidColorPanel.h" - -namespace ofxInterface { - - -void SolidColorPanel::setup(float w, float h) -{ - setSize(w, h); - - bDrawBackground = true; - bDrawBorder = false; - bRounded = false; - roundAngle = 5; -} - -void SolidColorPanel::draw() -{ - if (bDrawBackground) { - ofFill(); - ofSetColor(bgColor); - - if (bRounded) { - ofDrawRectRounded(0, 0, getWidth(), getHeight(), roundAngle); - } - else { - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } - } - - if (bDrawBorder) { - ofNoFill(); - ofSetLineWidth(2); - ofSetColor(strokeColor); - if (bRounded) { - ofDrawRectRounded(0, 0, getWidth(), getHeight(), roundAngle); - } - else { - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } - } -} - - - -} // namespace \ No newline at end of file diff --git a/src/components/SolidColorPanel.h b/src/components/SolidColorPanel.h deleted file mode 100644 index f7cb4dc..0000000 --- a/src/components/SolidColorPanel.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// SolidColorPanel.h -// ofxInterface -// -// Created by Gal Sasson on 8/13/14. -// -// - -#ifndef __ofxInterface__SolidColorPanel__ -#define __ofxInterface__SolidColorPanel__ - -#include -#include "ofMain.h" - -#include "ofxInterface.h" - -namespace ofxInterface { - - -class SolidColorPanel : public ofxInterface::Node -{ -public: - void setup(float w, float h); - - void setBGColor(const ofColor& c) { bgColor = c; } - void setStrokeColor(const ofColor& c) { strokeColor = c; } - void setBackground(bool set) { bDrawBackground = set; } - void setBorder(bool set) { bDrawBorder = set; } - void setRounded(bool set) { bRounded = set; } - void setRoundAngle(float ang) { roundAngle = ang; } - - void draw(); - -private: - bool bDrawBorder; - bool bDrawBackground; - bool bRounded; - float roundAngle; - - ofColor bgColor; - ofColor strokeColor; -}; - -} // namespace - -#endif /* defined(__ofxInterface__SolidColorPanel__) */ diff --git a/src/components/TextField.cpp b/src/components/TextField.cpp new file mode 100644 index 0000000..0f2e432 --- /dev/null +++ b/src/components/TextField.cpp @@ -0,0 +1,65 @@ +#include "TextField.h" + + +namespace ofxInterface { + TextField::TextField() :Node() + { + + } + + + TextField::~TextField() + { + } + + void TextField::draw() + { + vector blocks; + ofxFontStash2::Parser::parseText(text, font->getStyles(), "text", blocks); + int y = font->getTextBounds(blocks[0].text, blocks[0].style, 0, 0).height * 0.5; + + alignmentRenderFbo.begin(); + auto bounds = font->drawFormattedColumn(text, 0, y, getWidth(), horzAlignment); + alignmentRenderFbo.end(); + //float dy = bounds.height * 0.5; + + // since fontstash uses its own clip functions, we need to manually disable the draw + // masks out the text completely, when out of clip + // need to find a more appropriate function + ofRectangle boundsGlobal(toGlobal(ofVec3f(bounds.x, bounds.y)), bounds.width, bounds.height); + //boundsGlobal.y -= bounds.height; + // + //boundsGlobal.width -= boundsGlobal.x; + //boundsGlobal.height -= boundsGlobal.y; + + + auto cl = getGlobalRenderClip(); + auto in = getRenderClipRect().intersects(boundsGlobal); + if (!cl || in) { + font->drawFormattedColumn(text, 0, y, getWidth(), horzAlignment); + } + + + + } + + void TextField::setup(TextFieldSettings s) + { + Node::setup(s); + + font = s.font; + text = s.text; + horzAlignment = s.horzAlignment; + + alignmentRenderFbo.allocate(1, 1); + } + + void TextField::setText(string text_) + { + text = text_; + } + + void TextField::setAlignment(ofAlignHorz alignment_) { + horzAlignment = alignment_; + } +} \ No newline at end of file diff --git a/src/components/TextField.h b/src/components/TextField.h new file mode 100644 index 0000000..25ab16b --- /dev/null +++ b/src/components/TextField.h @@ -0,0 +1,33 @@ +#pragma once +#include "Node.h" +#include "ofxFontStash2.h" +#include "ofxFontStashStyle.h" + +namespace ofxInterface { + struct TextFieldSettings : NodeSettings{ + shared_ptr font; + string text; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + }; + + class TextField : + public Node + { + public: + TextField(); + ~TextField(); + + virtual void draw(); + virtual void setup(TextFieldSettings s); + void setText(string text); + void setAlignment(ofAlignHorz horzAlignment); + + private: + shared_ptr font; + string text; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + + ofFbo alignmentRenderFbo; + }; +} + diff --git a/src/components/TextInput.cpp b/src/components/TextInput.cpp new file mode 100644 index 0000000..54d1051 --- /dev/null +++ b/src/components/TextInput.cpp @@ -0,0 +1,358 @@ +#include "TextInput.h" +#include "NodeToJsonHelper.h" + +ofxInterface::TextInput::~TextInput() +{ + for (auto& e : keyInputs) { + ofRemoveListener(e, this, &TextInput::keyPressed); + } +} + +void ofxInterface::TextInput::setup(TextInputSettings settings) +{ + settings.type = RADIO; + ModalElement::setup(settings); + descriptionText = settings.descriptionText; + maxChars = settings.maxChars; + + font = settings.font; + style = settings.style; + styleInactive = style; + styleInactive.color = colorInactive; + enableNewline = settings.enableNewline; + horzAlignment = settings.horzAlignment; + autoResize = settings.autoResize; + + if (autoResize) { + resizeField(); + } + + // listener + ofAddListener(eventTouchDown, this, &TextInput::onTouchDown); + ofAddListener(eventTouchMove, this, &TextInput::onTouchMove); + +} + +void ofxInterface::TextInput::draw() +{ + auto baseRect = font->getTextBounds("X", style, 0, 0); + float dy = -baseRect.y + (getHeight() - baseRect.height)*0.75; + + if (enableNewline) { + dy = -baseRect.y; + } + // draw content + if (content.get().size() == 0) { + font->drawColumn(descriptionText, styleInactive, 0, dy, getWidth(), horzAlignment); + //font->draw(descriptionText, styleInactive, 0, dy); + }else { + //font->draw(content, style, 0, dy); + font->drawColumn(content, style, 0, dy, getWidth(), horzAlignment); + } + + // draw index pointer + if (isSelected) { + ofSetColor(colorActive); + if (enableNewline) { + vector blocks; + blocks.emplace_back(ofxFontStash2::StyledText({ content.get().substr(0, indexPointer), style })); + vector lines; + font->layoutLines(blocks, getWidth(), lines, horzAlignment, 0); + + int wLine = 0; + if (lines.size() > 0) { + wLine = lines.back().lineW; + if (lines.back().elements.size() > 0) { + wLine += lines.back().elements.front().x; + } + else if (horzAlignment == OF_ALIGN_HORZ_CENTER) { + wLine = getWidth()*0.5; + } + else if (horzAlignment == OF_ALIGN_HORZ_RIGHT) { + wLine = getWidth(); + } + } + float hCursor = font->getTextBounds("|", style, 0, 0).height; + + ofDrawRectangle(baseRect.x + wLine, -hCursor - baseRect.y + baseRect.height*(lines.size() - 1), 4, hCursor); + } + else { + auto indexRect = font->getTextBounds(content.get().substr(0, indexPointer), style, 0, 0); + ofDrawRectangle(baseRect.x + indexRect.width, 0, 4, getHeight()); + } + } + +} + +void ofxInterface::TextInput::keyPressed(ofKeyEventArgs & e) +{ + if (isSelected) { + if (e.key == 356) { // left + if (indexPointer > 0) { + indexPointer--; + string in = ""; + in += content.toString()[indexPointer - 1]; + if (in == "Ã") indexPointer--; + } + } + if (e.key == 358) { // right + if (indexPointer <= content->size()) { + indexPointer++; + string in = ""; + in += content.toString()[indexPointer]; + if (in == "Ã") indexPointer++; + } + } + if (e.key == 8) { // backspace + if (indexPointer > 0) { + content = content.toString().erase(indexPointer - 1, 1); + indexPointer--; + string in = ""; + in += content.toString()[indexPointer]; + if (in == "Ã") { + indexPointer--; + content = content.toString().erase(indexPointer - 1, 1); + } + } + } + + if (e.key == 13) { // return + if (enableNewline) { + if (maxChars == 0 || maxChars > content.get().size()) { + string in = "\n"; + if (indexPointer < content->size()) { + content = content.toString().insert(indexPointer, in); + } + else { + content += in; + } + indexPointer += in.size(); + } + } + else { + string t = content.get(); + ofNotifyEvent(returnPressedEvent, t); + } + + } + + else if (e.key >= 32 && e.key <= 252) { + string in = ""; + //only max characters allowed + if (maxChars == 0 || maxChars > content.get().size()) + { + // number field only accepts numbers + if (e.key == 252) // ü + { + in += "ü"; + } + else if (e.key == 220) // Ü + { + in += "Ü"; + } + else if (e.key == 246) // ö + { + in += "ö"; + } + else if (e.key == 214) // Ö + { + in += "Ö"; + } + else if (e.key == 228) // ä + { + in += "ä"; + } + else if (e.key == 196) // ä + { + in += "Ä"; + } + else if (e.key == 223) // ß + { + in += "ß"; + } + else if (e.key == 8364) // € + { + in += "€"; + } + + else { in += e.key; } + + if (indexPointer < content->size()) { + content = content.toString().insert(indexPointer, in); + } + else { + content += in; + } + indexPointer += in.size(); + } + } + sendContentChanged(content); + } +} + +string ofxInterface::TextInput::getContent() +{ + return content; +} + +void ofxInterface::TextInput::setContent(string content_) +{ + if (content.toString() != content_) { + content = content_; + indexPointer = content->size(); + sendContentChanged(content); + } +} + +void ofxInterface::TextInput::onSetContent(string & content) +{ + setContent(content); + sendContentChanged(content); +} + +void ofxInterface::TextInput::setSize(float w, float h) +{ + // resize font if external resizing + if (autoResize) { + // scale to int to reduce jitter while scaling + int newFontSize = style.fontSize * (w/getWidth()); + style.fontSize = newFontSize; + styleInactive.fontSize = newFontSize; + resizeField(); + } + + // proceed font resizing + Node::setSize(w, h); +} + +void ofxInterface::TextInput::setSize(const ofVec2f & s) +{ + setSize(s.x, s.y); +} + +void ofxInterface::TextInput::setAlignment(ofAlignHorz alignment_) +{ + horzAlignment = alignment_; +} + +void ofxInterface::TextInput::setFont(string fontId) +{ + style.fontID = fontId; + styleInactive.fontID = fontId; + resizeField(); +} + +void ofxInterface::TextInput::setColor(ofColor main, ofColor inactive) +{ + style.color = main; + styleInactive.color = inactive; +} + +void ofxInterface::TextInput::registerKeyInput(ofEvent& e) +{ + ofAddListener(e, this, &TextInput::keyPressed); + keyInputs.push_back(e); +} + +void ofxInterface::TextInput::unregisterKeyInput(ofEvent & e) +{ + ofRemoveListener(e, this, &TextInput::keyPressed); + +} + +void ofxInterface::TextInput::onTouchDown(TouchEvent & event) +{ + setIndexPosition(event.position - getPosition()); +} + +void ofxInterface::TextInput::onTouchMove(TouchEvent & event) +{ + setIndexPosition(event.position - getPosition()); +} + +ofxFontStash2::Style ofxInterface::TextInput::getFontStyle() +{ + return style; +} + +ofJson ofxInterface::TextInput::getNodeJson() +{ + auto ret = ModalElement::getNodeJson(); + ret["nodeType"] = "TextInput"; + ret["maxChars"] = maxChars; + ret["descriptionText"] = descriptionText; + ret["enableNewline"] = enableNewline; + ret["autoResize"] = autoResize; + + switch (horzAlignment) { + case OF_ALIGN_HORZ_LEFT: + ret["alignment"] = "left"; + break; + case OF_ALIGN_HORZ_CENTER: + ret["alignment"] = "center"; + break; + default: + ret["alignment"] = "right"; + break; + } + ret["text"] = content.get(); + ret["style"] = reddo::NodeToJson::styleToJson(style); + return ret; + +} + +void ofxInterface::TextInput::sendContentChanged(string content) +{ + string send = content; + + if (autoResize) { + resizeField(); + } + + ofNotifyEvent(eventContentChanged, send); +} + +void ofxInterface::TextInput::setIndexPosition(ofVec2f touch) +{ + // get corresponding cursor pos + float d = 99999999; + float iNew = 0; + if (font->getTextBounds(content.get(), style, 0, 0).width < touch.x){ + iNew = content.get().size(); + } else { + for (size_t i = 0; i < content.get().size(); i++) { + float p = font->getTextBounds(content.get().substr(0, i), style, 0, 0).width; + if (abs(touch.x - p) < d) { + d = abs(touch.x - p); + iNew = i; + } + } + } + + indexPointer = iNew; +} + +void ofxInterface::TextInput::resizeField() +{ + string text = content.get().size() > 0 ? content.get() : descriptionText; + auto baseRect = font->getTextBounds("X", style, 0, 0); + float dy = -baseRect.y - baseRect.height*0.75; + + if (enableNewline) { + //dy = -baseRect.y; + } + + int w = 0; + auto lines = ofSplitString(text, "\n"); + for (auto& line : lines) { + float wt = font->getTextBounds(line, style, 0, 0).width; + if (wt > w) { + w = wt; + } + } + + + + // use parent function, since class function is to be resizing font as well + Node::setSize(w + 1, dy + lines.size()*baseRect.height); +} diff --git a/src/components/TextInput.h b/src/components/TextInput.h new file mode 100644 index 0000000..9e605f4 --- /dev/null +++ b/src/components/TextInput.h @@ -0,0 +1,88 @@ +#pragma once + +#include "ModalElement.h" +#include "ofxFontStash2.h" + +namespace ofxInterface { + struct TextInputSettings : public ModalElementSettings { + shared_ptr font; + ofxFontStash2::Style style; + + int maxChars = 30; + string descriptionText; + bool enableNewline = false; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + + bool autoResize = false; + + }; + + class TextInput : public ModalElement + { + public: + ~TextInput(); + void setup(TextInputSettings settings); + + virtual void draw() override; + virtual void keyPressed(ofKeyEventArgs & e); + string getContent(); + void setContent(string content); + void onSetContent(string& content); + + void setSize(float w, float h) override; + virtual void setSize(const ofVec2f& s) override; + + void setAlignment(ofAlignHorz horzAlignment); + void setFont(string fontId); + void setColor(ofColor main, ofColor inactive); + + ofEvent eventContentChanged; + ofEvent returnPressedEvent; + + void registerKeyInput(ofEvent & e); + void unregisterKeyInput(ofEvent & e); + + void onTouchDown(TouchEvent& event); + void onTouchMove(TouchEvent& event); + + template + void addListener(ListenerClass * listener, ListenerMethod method) { + content.addListener(listener, method); + } + + template + void removeListener(ListenerClass * listener, ListenerMethod method) { + content.removeListener(listener, method); + } + + + + ofxFontStash2::Style getFontStyle(); + + ofJson getNodeJson() override; + + protected: + void sendContentChanged(string content); + void setIndexPosition(ofVec2f touch); + void resizeField(); + + shared_ptr font; + ofxFontStash2::Style style; + ofxFontStash2::Style styleInactive; + ofAlignHorz horzAlignment = OF_ALIGN_HORZ_LEFT; + + ofParameter content; + string descriptionText; + int indexPointer; + + bool isNumberField; + int maxChars; + bool enableNewline; + bool autoResize; + + vector< ofEvent> keyInputs; + + private: + }; + +} \ No newline at end of file diff --git a/src/components/TextureButton.cpp b/src/components/TextureButton.cpp deleted file mode 100644 index 2d7c85f..0000000 --- a/src/components/TextureButton.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// TextureButton.cpp -// ofxInterface -// -// Created by Gal Sasson on 7/24/14. -// -// - -#include "TextureButton.h" - -namespace ofxInterface -{ - - -TextureButton::TextureButton() -{ - texture = NULL; -} - -void TextureButton::setup(ofTexture *tex, float x, float y) -{ - texture = tex; - setSize(tex->getWidth(), tex->getHeight()); - - setPosition(x, y, 0); - - setPadding(0, 0, 0, 0); - - bDrawBackground = false; - bDrawBorder = false; - - bgColor = ofColor(255);; - tintColor = ofColor(255); - borderColor = ofColor(0); -} - -void TextureButton::setTexture(ofTexture* tex) -{ - texture = tex; - setSize(tex->getWidth(), tex->getHeight()); -} - -void TextureButton::setPadding(float top, float right, float bottom, float left) -{ - padTop = top; - padRight = right; - padBottom = bottom; - padLeft = left; -} - -void TextureButton::update(float dt) -{ - -} - -void TextureButton::draw() -{ - if (bDrawBackground) { - ofSetColor(bgColor); - ofFill(); - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } - - ofSetColor(tintColor); - if (texture) { - texture->draw(padLeft, padBottom, getWidth()-padLeft-padRight, getHeight()-padTop-padBottom); - } - - if (bDrawBorder) { - ofSetColor(borderColor); - ofNoFill(); - ofDrawRectangle(0, 0, getWidth(), getHeight()); - } -} - -} // namespace diff --git a/src/components/TextureButton.h b/src/components/TextureButton.h deleted file mode 100644 index 08f72da..0000000 --- a/src/components/TextureButton.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// TextureButton.h -// ofxUINode -// -// Created by Gal Sasson on 7/24/14. -// -// - -#ifndef __ofxInterface__TextureButton__ -#define __ofxInterface__TextureButton__ - -#include -#include "ofMain.h" -#include "ofxInterface.h" - -namespace ofxInterface -{ - -class TextureButton : public Node -{ -public: - TextureButton(); - void setup(ofTexture *tex, float x=0, float y=0); - - void setTexture(ofTexture *tex); - - void setBorder(bool draw) { bDrawBorder = draw; } - void setBackground(bool draw) { bDrawBackground = draw; } - - void setTintColor(const ofColor& c) { tintColor = c; } - void setBGColor(const ofColor& c) { bgColor = c; } - void setBorderColor(const ofColor& c) { borderColor = c; } - - void setPadding(float top, float right, float bottom, float left); - - void update(float dt); - void draw(); - - - -private: - - float padTop, padRight, padBottom, padLeft; - - bool bDrawBorder; - bool bDrawBackground; - - ofColor tintColor; - ofColor bgColor; - ofColor borderColor; - - ofTexture* texture; -}; - -} // namespace - -#endif /* defined(__BaseApp__TextureButton__) */ diff --git a/src/components/TextureNode.cpp b/src/components/TextureNode.cpp new file mode 100644 index 0000000..beca994 --- /dev/null +++ b/src/components/TextureNode.cpp @@ -0,0 +1,159 @@ +#include "TextureNode.h" + + +namespace ofxInterface { + TextureNode::TextureNode():Node() + { + } + + + TextureNode::~TextureNode() + { + } + + Node * TextureNode::clone() + { + auto ret = new TextureNode(*this); + return ret; + } + + void TextureNode::setup(ofTexture& texture) + { + setTexture(texture); + setSize(texture.getWidth(), texture.getHeight()); + } + + void TextureNode::setup(TextureNodeSettings settings) { + Node::setup(settings); + if (settings.size == ofVec2f(0, 0)) { + setup(settings.texture); + } else { + texture = settings.texture; + } + scaleMode = settings.scaleMode; + blendmode = settings.blendmode; + tinting = settings.tinting; + horizontalAlign = settings.horizontalAlign; + verticalAlign = settings.verticalAlign; + } + + void TextureNode::draw() + { + if (texture.isAllocated()) { + + if (blendmode == OF_BLENDMODE_ALPHA) { + glEnable(GL_BLEND); + glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); + glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } else { + setBlendMode(blendmode); + } + + if (alpha<255) + { + ofSetColor(255, alpha); + } + + ofRectangle dimensions{ 0,0,getWidth(),getHeight() }; + ofRectangle texCoords{ 0,0,texture.getWidth(),texture.getHeight() }; + + ofRectangle dimensionsT = dimensions; + ofRectangle texCoordst = texCoords; + switch (scaleMode) { + case OF_SCALEMODE_FIT: + texCoordst.scaleTo(dimensionsT, OF_ASPECT_RATIO_KEEP, horizontalAlign, verticalAlign); + //texCoordst.scaleTo(dimensionsT, OF_SCALEMODE_FIT); + dimensions = texCoordst; + break; + case OF_SCALEMODE_FILL: + //dimensionsT.scaleTo(texCoords, OF_SCALEMODE_FIT); + dimensionsT.scaleTo(texCoords, OF_ASPECT_RATIO_KEEP, horizontalAlign, verticalAlign); + texCoords = dimensionsT; + break; + case OF_SCALEMODE_CENTER: + break; + case OF_SCALEMODE_STRETCH_TO_FILL: + break; + default: + break; + } + + ofSetColor(tinting); + texture.drawSubsection(dimensions, texCoords); + } + + } + + void TextureNode::setTexture(ofTexture& texture_) + { + texture = texture_; + ofNotifyEvent(eventTextureChanged,texture); + } + + ofTexture TextureNode::getTexture() + { + return texture; + } + + float TextureNode::getAlpha() { + return alpha; + } + + void TextureNode::setAlpha(float alpha_) + { + alpha = alpha_; + } + + void TextureNode::setTinting(ofColor color) + { + tinting = color; + } + + ofAlignVert TextureNode::getVerticalAlign() + { + return verticalAlign; + } + + void TextureNode::setVerticalAlign(ofAlignVert align) + { + verticalAlign = align; + } + + ofAlignHorz TextureNode::getHorizontalAlign() + { + return horizontalAlign; + } + + void TextureNode::setHorizontalAlign(ofAlignHorz align) + { + horizontalAlign = align; + } + + + ofScaleMode TextureNode::getScaleMode() { + return scaleMode; + } + + void TextureNode::setScaleMode(ofScaleMode mode) { + scaleMode = mode; + } + + ofColor TextureNode::getTinting() { + return tinting; + } + + void TextureNode::setBlendMode(ofBlendMode blendmode_) { + blendmode = blendmode_; + } + + ofJson TextureNode::getNodeJson() { + auto ret = Node::getNodeJson(); + ret["nodeType"] = "TextureNode"; + ret["tint"] = vector{ tinting.r,tinting.g,tinting.b,tinting.a }; + ret["alpha"] = alpha; + ret["scaleMode"] = "fill"; + return ret; + } + +} \ No newline at end of file diff --git a/src/components/TextureNode.h b/src/components/TextureNode.h new file mode 100644 index 0000000..a509761 --- /dev/null +++ b/src/components/TextureNode.h @@ -0,0 +1,65 @@ +#pragma once +#include "Node.h" + +namespace ofxInterface { + struct TextureNodeSettings : NodeSettings { + ofTexture texture; + ofColor tinting = ofColor(255); + float alpha = 255; + ofScaleMode scaleMode = OF_SCALEMODE_FILL; + ofBlendMode blendmode = OF_BLENDMODE_ALPHA; + ofAlignHorz horizontalAlign = OF_ALIGN_HORZ_CENTER; + ofAlignVert verticalAlign = OF_ALIGN_VERT_CENTER; + }; + + class TextureNode : + public Node + { + public: + TextureNode(); + ~TextureNode(); + virtual Node* clone() override; + + void setup(ofTexture& texture); + void setup(TextureNodeSettings settings); + + virtual void draw() override; + + void setTexture(ofTexture& texture_); + ofTexture getTexture(); + + ofScaleMode getScaleMode(); + void setScaleMode(ofScaleMode mode); + + float getAlpha(); + void setAlpha(float alpha); + + ofColor getTinting(); + void setTinting(ofColor color); + + ofAlignVert getVerticalAlign(); + void setVerticalAlign(ofAlignVert align); + + ofAlignHorz getHorizontalAlign(); + void setHorizontalAlign(ofAlignHorz align); + + void setBlendMode(ofBlendMode blendmode); + + ofEvent eventTextureChanged; + + ofJson getNodeJson() override; + + protected: + ofTexture texture; + float alpha; + + private: + ofColor tinting = ofColor(255); + ofScaleMode scaleMode = OF_SCALEMODE_FILL; + ofBlendMode blendmode = OF_BLENDMODE_ALPHA; + + ofAlignHorz horizontalAlign = OF_ALIGN_HORZ_CENTER; + ofAlignVert verticalAlign = OF_ALIGN_VERT_CENTER; + }; +} + diff --git a/src/effects/EffectDropShadow.cpp b/src/effects/EffectDropShadow.cpp new file mode 100644 index 0000000..00f6d07 --- /dev/null +++ b/src/effects/EffectDropShadow.cpp @@ -0,0 +1,99 @@ +#include "EffectDropShadow.h" + + +namespace ofxInterface +{ + EffectDropShadow::EffectDropShadow() + { + //shaderShadow.load("shaders/blur"); + if (ofIsGLProgrammableRenderer()) { + ofLogVerbose("EffectDropShadow.loadShaders()") << "Programmable renderer detected."; + ofLogVerbose("EffectDropShadow.loadShaders()") << "Loading shader......"; + shaderShadow.setupShaderFromSource(GL_VERTEX_SHADER, shaderDef.vertex); + shaderShadow.setupShaderFromSource(GL_FRAGMENT_SHADER, shaderDef.fragment); + shaderShadow.bindDefaults(); + shaderShadow.linkProgram(); + } + else { + ofLogError("EffectDropShadow", "DropShadow only available for glsl >= ver.3"); + } + } + + + EffectDropShadow::~EffectDropShadow() + { + } + + void EffectDropShadow::setup(EffectDropShadowSettings s) + { + settings = s; + } + + void EffectDropShadow::update(ofFbo in) + { + // only update if size > 0 + if (settings.size > 0) { + int wFbo = in.getWidth() + settings.size * 2; + int hFbo = in.getHeight() + settings.size * 2; + + // allocate new fbo + if (!fboBuffer.isAllocated() || + (settings.isDynamic && + (wFbo != fboBuffer.getWidth() || + hFbo != fboBuffer.getHeight()) + ) + ) { + fboBuffer.allocate(wFbo, hFbo); + fboShadow.allocate(wFbo, hFbo); + + generateShadow(in,fboBuffer, fboShadow, settings); + } + + // update filter + if ((settings.isDynamic && + (wFbo != fboBuffer.getWidth() || + hFbo != fboBuffer.getHeight()) + ) + ) { + generateShadow(in,fboBuffer, fboShadow, settings); + } + } + } + + ofFbo EffectDropShadow::getShadow() + { + return fboShadow; + } + + + void EffectDropShadow::checkAllocated(ofFbo& fbo, int width, int height) + { + if (!fbo.isAllocated() || fbo.getWidth() != width || fbo.getHeight() != height) { + fbo.allocate(width, height); + } + } + void EffectDropShadow::generateShadow(ofFbo in, ofFbo buffer, ofFbo& out, EffectDropShadowSettings settings) + { + + ofFloatColor c = ofFloatColor(settings.color); + + // create buffer fbo + buffer.begin(); + ofClear(0, 0); + ofSetColor(255); + in.draw(settings.size, settings.size); + buffer.end(); + + // apply filter + out.begin(); + shaderShadow.begin(); + shaderShadow.setUniform1i("kernelSize", 40); + shaderShadow.setUniform4f("shadowColor", ofVec4f(c.r, c.g, c.b, c.a)); + shaderShadow.setUniform2f("offset", ofVec2f(settings.size/20)); + + buffer.draw(0, 0); + shaderShadow.end(); + out.end(); + + } +} \ No newline at end of file diff --git a/src/effects/EffectDropShadow.h b/src/effects/EffectDropShadow.h new file mode 100644 index 0000000..c05e6ec --- /dev/null +++ b/src/effects/EffectDropShadow.h @@ -0,0 +1,39 @@ +#pragma once + +#include "ofMain.h" +#include "ofShader.h" +#include "EffectDropShadowShader.h" + +namespace ofxInterface +{ + + struct EffectDropShadowSettings { + ofColor color = ofColor(0); + ofVec2f position = ofVec2f(0); + float size = 0; + bool isDynamic = false; // todo; + }; + + class EffectDropShadow + { + public: + EffectDropShadow(); + ~EffectDropShadow(); + + void setup(EffectDropShadowSettings s); + void update(ofFbo in); + ofFbo getShadow(); + + private: + void checkAllocated(ofFbo& fbo, int width, int height); + void generateShadow(ofFbo in, ofFbo buffer, ofFbo& out, EffectDropShadowSettings settings); + + ofShader shaderShadow; + EffectDropShadowShader shaderDef; + + ofFbo fboBuffer; + ofFbo fboShadow; + EffectDropShadowSettings settings; + }; + +} \ No newline at end of file diff --git a/src/effects/EffectDropShadowShader.h b/src/effects/EffectDropShadowShader.h new file mode 100644 index 0000000..58cfad5 --- /dev/null +++ b/src/effects/EffectDropShadowShader.h @@ -0,0 +1,78 @@ +#pragma once + +#define STRINGIFY(A) #A + +#include "ofMain.h" + +class EffectDropShadowShader { +public: string vertex, fragment; + + EffectDropShadowShader() { + vertex = "#version 150\n"; + vertex += STRINGIFY( + uniform mat4 projectionMatrix; + uniform mat4 modelViewMatrix; + uniform mat4 modelViewProjectionMatrix; + + in vec4 position; + in vec2 texcoord; + + out vec2 texCoordVarying; + + void main() + { + texCoordVarying = texcoord; + gl_Position = modelViewProjectionMatrix * position; + } + ); + + // === Base mask ============================================================================ + fragment = "#version 150\n"; + fragment += STRINGIFY( + // Input params + uniform sampler2DRect tex0; + uniform int kernelSize; + uniform vec2 offset; + uniform vec4 shadowColor; + + in vec2 texCoordVarying; + out vec4 outputColor; + + void main() + { + vec4 accumColor = vec4(0.); + + + vec2 st = texCoordVarying; + float kernelSizef = float(kernelSize); + + + float accumWeight = 0.; + const float k = .15915494; // 1 / (2*PI) + float kernelSize2 = kernelSizef * kernelSizef; + + for (int j = 0; j < kernelSize; j++) { + float y = -.5 * (kernelSizef - 1.) + float(j); + for (int i = 0; i < kernelSize; i++) { + //if(distance(st, st-vec2(j,i))<=kernelSizef ){ // radial filter + float x = -.5 * (kernelSizef - 1.) + float(i); + float weight = (k / kernelSizef) * exp(-(x * x + y * y) / (2. * kernelSize2)); + accumColor += weight * vec4(shadowColor.r, shadowColor.g, shadowColor.b, texture(tex0, st + vec2(x, y) * offset).a); + accumWeight += weight; + // } + + + } + } + accumColor.a*= shadowColor.a; + //outputColor = shadowColor; + outputColor = accumColor / accumWeight; + //outputColor = texture(tex0, st); + + + + } + ); + + } +}; \ No newline at end of file diff --git a/src/effects/GradientShader.cpp b/src/effects/GradientShader.cpp new file mode 100644 index 0000000..30412bb --- /dev/null +++ b/src/effects/GradientShader.cpp @@ -0,0 +1,250 @@ +#include "GradientShader.h" + +const ofShader ofxInterface::getGradientShader3Colors() +{ + ofShader shader; + + string vertex = "#version 150\n"; + vertex += STRINGIFY( + uniform mat4 modelViewProjectionMatrix; + + in vec4 position; + + void main() { + gl_Position = modelViewProjectionMatrix * position; + } + ); + + string frag = "#version 150\n"; + frag += "#define SRGB_TO_LINEAR(c) pow((c), vec4(2.2))\n"; + frag += "#define LINEAR_TO_SRGB(c) pow((c), vec4(1.0 / 2.2))\n"; + frag += "#define SRGB(r, g, b, a) SRGB_TO_LINEAR(vec4(float(r), float(g), float(b),float(a)) / 255.0)\n"; + + frag += STRINGIFY( + uniform vec4 color1; + uniform vec4 color2; + uniform vec4 color3; + + uniform vec2 begin; + uniform vec2 end; + uniform float mid; + uniform vec2 size; + + out vec4 outputColor; + + // Gradient noise from Jorge Jimenez's presentation: + // http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare + float gradientNoise(vec2 uv) + { + const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(magic.z * fract(dot(uv, magic.xy))); + } + + void main() { + + vec4 scolor1 = SRGB(color1.x, color1.y, color1.z, color1.w); + vec4 scolor2 = SRGB(color2.x, color2.y, color2.z, color2.w); + vec4 scolor3 = SRGB(color3.x, color3.y, color3.z, color3.w); + + vec2 fragCoord = gl_FragCoord.xy / size; + + // Calculate interpolation factor with vector projection. + vec2 ba = end - begin; + float t = dot(fragCoord - begin, ba) / dot(ba, ba); + // Saturate and apply smoothstep to the factor. + vec4 color; + if (t < mid) { + t = smoothstep(0.0, mid, clamp(t, 0.0, 1.0)); + color = mix(scolor1, scolor2, t); + } + else { + t = smoothstep(mid, 1.0, clamp(t, 0.0, 1.0)); + color = mix(scolor2, scolor3, t); + } + + // Convert color from linear to sRGB color space (=gamma encode). + color = LINEAR_TO_SRGB(color); + + // Add gradient noise to reduce banding. + color += (1.0 / 255.0) * gradientNoise(fragCoord) - (0.5 / 255.0); + + outputColor = vec4(color); + } + ); + + shader.unload(); + shader.setupShaderFromSource(GL_VERTEX_SHADER, vertex); + shader.setupShaderFromSource(GL_FRAGMENT_SHADER, frag); + shader.bindDefaults(); + shader.linkProgram(); + + return shader; +} + +const ofShader ofxInterface::getGradientCircularMaskShader() +{ + ofShader shader; + + string vertex = "#version 150\n"; + vertex += STRINGIFY( + uniform mat4 modelViewProjectionMatrix; + + in vec4 position; + + void main() { + gl_Position = modelViewProjectionMatrix * position; + } + ); + + string frag = "#version 150\n"; + frag += "#define SRGB_TO_LINEAR(c) pow((c), vec4(2.2))\n"; + frag += "#define LINEAR_TO_SRGB(c) pow((c), vec4(1.0 / 2.2))\n"; + frag += "#define SRGB(r, g, b, a) SRGB_TO_LINEAR(vec4(float(r), float(g), float(b),float(a)) / 255.0)\n"; + + frag += STRINGIFY( + uniform vec4 color1; + uniform vec4 color2; + uniform vec4 color3; + + uniform float radius; + uniform vec2 mouse; + uniform vec2 begin; + uniform vec2 end; + uniform float mid; + uniform vec2 size; + + out vec4 outputColor; + + // Gradient noise from Jorge Jimenez's presentation: + // http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare + float gradientNoise(vec2 uv) + { + const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(magic.z * fract(dot(uv, magic.xy))); + } + + void main() { + + vec4 scolor1 = SRGB(color1.x, color1.y, color1.z, color1.w); + vec4 scolor2 = SRGB(color2.x, color2.y, color2.z, color2.w); + vec4 scolor3 = SRGB(color3.x, color3.y, color3.z, color3.w); + + vec2 fragCoord = gl_FragCoord.xy / size; + + // Calculate interpolation factor with vector projection. + vec2 ba = end - begin; + float t = dot(fragCoord - begin, ba) / dot(ba, ba); + // Saturate and apply smoothstep to the factor. + vec4 color; + if (t < mid) { + t = smoothstep(0.0, mid, clamp(t, 0.0, 1.0)); + color = mix(scolor1, scolor2, t); + } + else { + t = smoothstep(mid, 1.0, clamp(t, 0.0, 1.0)); + color = mix(scolor2, scolor3, t); + } + + // Convert color from linear to sRGB color space (=gamma encode). + color = LINEAR_TO_SRGB(color); + + // Add gradient noise to reduce banding. + color += (1.0 / 255.0) * gradientNoise(fragCoord) - (0.5 / 255.0); + + // mask out shader + if (distance(gl_FragCoord.xy, mouse) > radius) { + color = vec4(0, 0, 0, 0); + } + + outputColor = vec4(color); + } + ); + + shader.unload(); + shader.setupShaderFromSource(GL_VERTEX_SHADER, vertex); + shader.setupShaderFromSource(GL_FRAGMENT_SHADER, frag); + shader.bindDefaults(); + shader.linkProgram(); + + return shader; +} + +const ofShader ofxInterface::getGradientShader2Colors() +{ + ofShader shader; + + string vertex = "#version 150\n"; + vertex += STRINGIFY( + uniform mat4 modelViewProjectionMatrix; + + in vec4 position; + + void main() { + gl_Position = modelViewProjectionMatrix * position; + } + ); + + string frag = "#version 150\n"; + frag += "#define SRGB_TO_LINEAR(c) pow((c), vec4(2.2))\n"; + frag += "#define LINEAR_TO_SRGB(c) pow((c), vec4(1.0 / 2.2))\n"; + frag += "#define SRGB(r, g, b, a) SRGB_TO_LINEAR(vec4(float(r), float(g), float(b),float(a)) / 255.0)\n"; + + frag += STRINGIFY( + uniform vec4 color1; + uniform vec4 color2; + + uniform vec2 begin; + uniform vec2 end; + uniform vec2 size; + + out vec4 outputColor; + + // Gradient noise from Jorge Jimenez's presentation: + // http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare + float gradientNoise(vec2 uv) + { + const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(magic.z * fract(dot(uv, magic.xy))); + } + + void main() { + + vec4 scolor1 = SRGB(color1.x, color1.y, color1.z, color1.w); + vec4 scolor2 = SRGB(color2.x, color2.y, color2.z, color2.w); + + vec2 fragCoord = gl_FragCoord.xy / size; + + // Calculate interpolation factor with vector projection. + vec2 ba = end - begin; + float t = dot(fragCoord - begin, ba) / dot(ba, ba); + // Saturate and apply smoothstep to the factor. + vec4 color; + + t = smoothstep(0.0, 1.0, clamp(t, 0.0, 1.0)); + color = mix(scolor1, scolor2, t); + + + + // Convert color from linear to sRGB color space (=gamma encode). + color = LINEAR_TO_SRGB(color); + + // Add gradient noise to reduce banding. + color += (1.0 / 255.0) * gradientNoise(fragCoord) - (0.5 / 255.0); + + outputColor = vec4(color); + } + ); + + shader.unload(); + shader.setupShaderFromSource(GL_VERTEX_SHADER, vertex); + shader.setupShaderFromSource(GL_FRAGMENT_SHADER, frag); + shader.bindDefaults(); + shader.linkProgram(); + + return shader; +} + +const ofVec4f ofxInterface::colorToVec4(ofColor c) +{ + return ofVec4f(c.r, c.g, c.b, c.a); +} diff --git a/src/effects/GradientShader.h b/src/effects/GradientShader.h new file mode 100644 index 0000000..77a199c --- /dev/null +++ b/src/effects/GradientShader.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ofMain.h" + +#define STRINGIFY(A) #A + +namespace ofxInterface { + const ofShader getGradientShader3Colors(); + const ofShader getGradientShader2Colors(); + const ofShader getGradientCircularMaskShader(); + const ofVec4f colorToVec4(ofColor c); +} \ No newline at end of file diff --git a/src/ofxInterface.h b/src/ofxInterface.h index 4f5afc7..455c454 100644 --- a/src/ofxInterface.h +++ b/src/ofxInterface.h @@ -9,18 +9,15 @@ #ifndef __ofxInterface_h__ #define __ofxInterface_h__ -#include "Node.h" -#include "AnimatableNode.h" #include "TouchEvent.h" #include "TouchManager.h" #include "VirtualTouch.h" /****** - * include default components + * include GuiFactory */ -#include "components/TextureButton.h" -#include "components/BitmapTextButton.h" -#include "components/SolidColorPanel.h" -#include "components/LambdaView.h" +#include "GuiFactory.h" + +#include "AnimatableNode.h" #endif