Skip to content

Instantly share code, notes, and snippets.

@sansumbrella
Last active December 12, 2015 05:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sansumbrella/4719988 to your computer and use it in GitHub Desktop.
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.
#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