Last active
December 12, 2015 05:09
-
-
Save sansumbrella/4719988 to your computer and use it in GitHub Desktop.
Rotating UI elements while maintaining physical location. Compile as Objective-C++ against the AppRewrite branch of Cinder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "cinder/app/AppNative.h" | |
#include "cinder/gl/gl.h" | |
#include "cinder/Timeline.h" | |
#include "cinder/Rand.h" | |
#import <UIKit/UIView.h> | |
#import <UIKit/UIApplication.h> | |
using namespace ci; | |
using namespace ci::app; | |
using namespace std; | |
static string orientationString( InterfaceOrientation orientation ) | |
{ | |
switch (orientation) { | |
case InterfaceOrientation::Portrait: return "Portrait"; | |
case InterfaceOrientation::PortraitUpsideDown: return "PortraitUpsideDown"; | |
case InterfaceOrientation::LandscapeLeft: return "LandscapeLeft"; | |
case InterfaceOrientation::LandscapeRight: return "LandscapeRight"; | |
default: return "Error"; | |
} | |
} | |
struct GridElement | |
{ | |
GridElement( const Rectf &rect, const Color &color ): | |
mColor( color ) | |
, mLoc( rect.getCenter() ) | |
, mCanonicalLoc( mLoc ) | |
, mHalfDiagonal( rect.getUpperLeft() - rect.getCenter() ) | |
, mRotation( 0 ) | |
{} | |
void transformCenter( const MatrixAffine2f &mat ) | |
{ mLoc = mat.transformPoint( mCanonicalLoc ); } | |
Rectf getBounds() const | |
{ return Rectf( mLoc + mHalfDiagonal, mLoc - mHalfDiagonal ); } | |
Color getColor() const { return mColor; } | |
void setColor( const Color &color ){ mColor = color; } | |
Vec2f getCenter() const { return mLoc; } | |
Anim<float>* getRotationAnim() { return &mRotation; } | |
float getRotation() const { return mRotation(); } | |
private: | |
ci::Color mColor; | |
ci::Vec2f mLoc, mCanonicalLoc; | |
ci::Vec2f mHalfDiagonal; | |
Anim<float> mRotation; | |
}; | |
class OrientationProjectApp : public AppNative { | |
public: | |
void prepareSettings( Settings *settings ); | |
void setup(); | |
void update(); | |
void draw(); | |
// update the content positions | |
void relayoutView(); | |
void rotateElements(); | |
void resize(); | |
float getOrientationAngle(); | |
void touchesBegan( TouchEvent event ); | |
private: | |
vector<GridElement> mElements; | |
//! the original center, from which we can calculate an offset/rotations | |
Vec2i mWindowCenter; | |
float mPreviousRotation = 0; | |
bool mApplyDelay = false; | |
}; | |
void OrientationProjectApp::prepareSettings(Settings *settings) | |
{ | |
settings->enableMultiTouch(); | |
settings->enableHighDensityDisplay(); | |
} | |
void OrientationProjectApp::setup() | |
{ | |
getSignalSupportedOrientations().connect( [](){ return InterfaceOrientation::All; } ); | |
mWindowCenter = getWindowCenter(); | |
relayoutView(); | |
auto layout_view = [this](){ relayoutView(); }; | |
auto rotate_elements = [this](){ rotateElements(); }; | |
getSignalWillRotate().connect( layout_view ); | |
getSignalDidRotate().connect( rotate_elements ); | |
UIView *view = (UIView*)getWindow()->getNative(); | |
// [UIView setAnimationsEnabled:NO]; | |
// set to Redraw if you want to see a warped ellipse. Other modes show various non-warped offsets. | |
[view setContentMode:UIViewContentModeRedraw]; | |
// [view setContentMode:UIViewContentModeCenter]; | |
// [view setContentMode:UIViewContentModeScaleAspectFit]; | |
int columns = 4; | |
int rows = 3; | |
float w = 80; | |
float h = 100; | |
float margin = 60; | |
float hue = 0.0f; | |
Vec2f offset = getWindowCenter() - Vec2f( columns * w + (columns - 1) * margin, rows * h + (rows - 1) * margin ) / 2; | |
for( int x = 0; x < columns; ++x ) | |
{ | |
for( int y = 0; y < rows; ++y ) | |
{ | |
float x1 = x * (w + margin) + offset.x; | |
float y1 = y * (h + margin) + offset.y; | |
Rectf bounds( x1, y1, x1 + w, y1 + h ); | |
mElements.push_back( GridElement( bounds, Color( CM_HSV, hue, 0.9f, 1.0f ) ) ); | |
hue += 0.175f; | |
if( hue > 1.0f ){ hue -= floor( hue ); } | |
} | |
} | |
} | |
float OrientationProjectApp::getOrientationAngle() | |
{ | |
float rotation = 0; | |
switch ( getInterfaceOrientation() ) | |
{ | |
case InterfaceOrientation::LandscapeLeft: | |
rotation = M_PI / 2; | |
break; | |
case InterfaceOrientation::PortraitUpsideDown: | |
rotation = M_PI; | |
break; | |
case InterfaceOrientation::LandscapeRight: | |
rotation = 3 * M_PI / 2; | |
break; | |
default: | |
break; | |
} | |
return rotation; | |
} | |
void OrientationProjectApp::relayoutView() | |
{ | |
// update our bounds and size for OpenGL matrices | |
console() << "Relayout to " << orientationString( getInterfaceOrientation() ) << endl; | |
if( mApplyDelay ) | |
{ | |
[UIView setAnimationsEnabled:YES]; | |
[UIView setAnimationDelay:0.2]; | |
[UIView setAnimationDuration:1.0]; | |
} | |
else | |
{ | |
[UIView setAnimationsEnabled:NO]; | |
[UIView setAnimationDuration:0]; | |
} | |
// reposition content | |
auto center = getWindowCenter(); | |
auto delta = center - mWindowCenter; | |
float rotation = getOrientationAngle(); | |
float rotation_delta = rotation - mPreviousRotation; | |
if( rotation_delta > M_PI + EPSILON_VALUE ) | |
{ | |
rotation_delta = M_PI - rotation_delta; | |
} | |
else if( rotation_delta < -M_PI - EPSILON_VALUE ) | |
{ | |
rotation_delta += M_PI; | |
rotation_delta *= -1; | |
} | |
MatrixAffine2f mat( MatrixAffine2f::identity() ); | |
// rotate around world position | |
mat.translate( center ); | |
mat.rotate( rotation ); | |
mat.translate( -center ); | |
mat.translate( delta ); | |
for( auto &e : mElements ) | |
{ // apply transform (and rotate to look like we're in previous position) | |
e.transformCenter( mat ); | |
*e.getRotationAnim() = rotation_delta * 180 / M_PI; | |
} | |
} | |
void OrientationProjectApp::rotateElements() | |
{ | |
mPreviousRotation = getOrientationAngle(); | |
float rot = 0.0f; | |
float delay = 0.0f; | |
for( auto &e : mElements ) | |
{ | |
auto opt = timeline().apply( e.getRotationAnim(), rot, 0.48f, EaseOutQuad() ); | |
opt.delay( delay ); | |
delay += 0.02f; | |
} | |
} | |
void OrientationProjectApp::resize() | |
{ | |
console() << "Resize" << endl; | |
relayoutView(); | |
} | |
void OrientationProjectApp::touchesBegan(cinder::app::TouchEvent event) | |
{ | |
for( auto &touch : event.getTouches() ) | |
{ | |
for( auto &e : mElements ) | |
{ | |
if( e.getBounds().contains( touch.getPos() ) ) | |
{ | |
e.setColor( Color( CM_HSV, Rand::randFloat(), 0.9f, 1.0f ) ); | |
} | |
} | |
} | |
mApplyDelay = !mApplyDelay; | |
[UIView setAnimationsEnabled:(mApplyDelay ? YES : NO)]; | |
} | |
void OrientationProjectApp::update() | |
{ | |
} | |
void OrientationProjectApp::draw() | |
{ | |
gl::clear( Color( 0, 0, 0 ) ); | |
gl::color( Color( 1.0f, 1.0f, 0.0f ) ); | |
for( auto &e : mElements ) | |
{ | |
gl::color( e.getColor() ); | |
gl::pushModelView(); | |
gl::translate( e.getCenter() ); | |
gl::rotate( e.getRotation() ); | |
gl::translate( -e.getCenter() ); | |
gl::drawSolidRect( e.getBounds() ); | |
gl::drawSolidEllipse( e.getBounds().getUpperLeft(), 6.0f, 6.0f ); | |
gl::popModelView(); | |
} | |
// for easier warp viewing, when it occurs | |
Color c = mApplyDelay ? Color( 1, 0, 0 ) : Color( 0, 1, 0 ); | |
gl::color( c ); | |
gl::drawSolidEllipse( getWindowCenter(), 32.0f, 32.0f ); | |
gl::drawSolidEllipse( getWindowCenter() - Vec2f( 0, 48.0f ), 16, 16 ); | |
} | |
CINDER_APP_NATIVE( OrientationProjectApp, RendererGl( RendererGl::AA_NONE ) ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment