X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Fcontrols%2Fpopup%2Fpopup-impl.cpp;h=d12e2734935e4d6ca0273594dbf7ee1a8d1f1832;hp=9e654cc7c0871bf5681512728bd968aae486a6c4;hb=306ab4095235efd0289e9da4e3994f456cbda2aa;hpb=e4e5db1d2d7997e7bf803a531048d8dcb959083b diff --git a/dali-toolkit/internal/controls/popup/popup-impl.cpp b/dali-toolkit/internal/controls/popup/popup-impl.cpp old mode 100755 new mode 100644 index 9e654cc..d12e273 --- a/dali-toolkit/internal/controls/popup/popup-impl.cpp +++ b/dali-toolkit/internal/controls/popup/popup-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Copyright (c) 2016 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,23 +20,25 @@ // EXTERNAL INCLUDES #include // for strcmp +#include +#include +#include #include -#include #include #include #include -#include -#include +#include #include -#include +#include #include -#include // INTERNAL INCLUDES -#include +#include #include -#include -#include +#include +#include +#include +#include #include using namespace Dali; @@ -53,504 +55,882 @@ namespace Internal namespace { +/** + * Creation function for main Popup type. + * @return Handle to the new popup object. + */ BaseHandle Create() { return Toolkit::Popup::New(); } -// Setup properties, signals and actions using the type-registry. -DALI_TYPE_REGISTRATION_BEGIN( Toolkit::Popup, Toolkit::Control, Create ) +// Toast style defaults. +const int DEFAULT_TOAST_AUTO_HIDE_DELAY = 3000; ///< Toast will auto-hide after 3000ms (3 seconds) +const float DEFAULT_TOAST_TRANSITION_TIME = 0.65f; ///< Default time the toast Popup will take to show and hide. +const Vector3 DEFAULT_TOAST_BOTTOM_PARENT_ORIGIN( 0.5f, 0.94f, 0.5f ); ///< This is similar to BOTTOM_CENTER, but vertically higher up, as a ratio of parent height. +const Vector3 DEFAULT_TOAST_WIDTH_OF_STAGE_RATIO( 0.75f, 0.75f, 0.75f ); ///< Amount of the stage's width that the toast popup will take up. -DALI_SIGNAL_REGISTRATION( Popup, "touched-outside", SIGNAL_TOUCHED_OUTSIDE ) -DALI_SIGNAL_REGISTRATION( Popup, "hidden", SIGNAL_HIDDEN ) +/** + * Creation function for named type "popupToast". + * @return Handle to the new toast popup object. + */ +BaseHandle CreateToast() +{ + Toolkit::Popup popup = Toolkit::Popup::New(); -DALI_TYPE_REGISTRATION_END() + // Setup for Toast Popup type. + popup.SetSizeModeFactor( DEFAULT_TOAST_WIDTH_OF_STAGE_RATIO ); + popup.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH ); + popup.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT ); + popup.SetProperty( Toolkit::Popup::Property::CONTEXTUAL_MODE, Toolkit::Popup::NON_CONTEXTUAL ); + popup.SetProperty( Toolkit::Popup::Property::ANIMATION_DURATION, DEFAULT_TOAST_TRANSITION_TIME ); + popup.SetProperty( Toolkit::Popup::Property::TAIL_VISIBILITY, false ); + + // Disable the dimmed backing. + popup.SetProperty( Toolkit::Popup::Property::BACKING_ENABLED, false ); + + // The toast popup should fade in (not zoom). + popup.SetProperty( Toolkit::Popup::Property::ANIMATION_MODE, Toolkit::Popup::FADE ); + + // The toast popup should auto-hide. + popup.SetProperty( Toolkit::Popup::Property::AUTO_HIDE_DELAY, DEFAULT_TOAST_AUTO_HIDE_DELAY ); + + // Align to the bottom of the screen. + popup.SetParentOrigin( DEFAULT_TOAST_BOTTOM_PARENT_ORIGIN ); + popup.SetAnchorPoint( AnchorPoint::BOTTOM_CENTER ); + + // Let events pass through the toast popup. + popup.SetProperty( Toolkit::Popup::Property::TOUCH_TRANSPARENT, true ); -// Properties -const char* const PROPERTY_TITLE = "title"; -const char* const PROPERTY_STATE = "state"; + return popup; +} + +// Setup properties, signals and actions using the type-registry. +DALI_TYPE_REGISTRATION_BEGIN( Toolkit::Popup, Toolkit::Control, Create ) -const float POPUP_ANIMATION_DURATION = 0.45f; ///< Duration of hide/show animations +// Main content related properties. +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "title", MAP, TITLE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "content", MAP, CONTENT ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "footer", MAP, FOOTER ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "displayState", STRING, DISPLAY_STATE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "touchTransparent", BOOLEAN, TOUCH_TRANSPARENT ) + +// Contextual related properties. +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailVisibility", BOOLEAN, TAIL_VISIBILITY ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailPosition", VECTOR3, TAIL_POSITION ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "contextualMode", STRING, CONTEXTUAL_MODE ) + +// Animation related properties. +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "animationDuration", FLOAT, ANIMATION_DURATION ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "animationMode", STRING, ANIMATION_MODE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "entryAnimation", MAP, ENTRY_ANIMATION ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "exitAnimation", MAP, EXIT_ANIMATION ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "autoHideDelay", INTEGER, AUTO_HIDE_DELAY ) + +// Style related properties. +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "backingEnabled", BOOLEAN, BACKING_ENABLED ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "backingColor", VECTOR4, BACKING_COLOR ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "popupBackgroundImage", STRING, POPUP_BACKGROUND_IMAGE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "popupBackgroundBorder", RECTANGLE, POPUP_BACKGROUND_BORDER ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailUpImage", STRING, TAIL_UP_IMAGE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailDownImage", STRING, TAIL_DOWN_IMAGE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailLeftImage", STRING, TAIL_LEFT_IMAGE ) +DALI_PROPERTY_REGISTRATION( Toolkit, Popup, "tailRightImage", STRING, TAIL_RIGHT_IMAGE ) + +// Signals. +DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "touchedOutside", SIGNAL_TOUCHED_OUTSIDE ) +DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "showing", SIGNAL_SHOWING ) +DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "shown", SIGNAL_SHOWN ) +DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "hiding", SIGNAL_HIDING ) +DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "hidden", SIGNAL_HIDDEN ) -const float POPUP_WIDTH = 720.0f; ///< Width of Popup -const float POPUP_OUT_MARGIN_WIDTH = 16.f; ///< Space between the screen edge and the popup edge in the horizontal dimension. -const float POPUP_OUT_MARGIN_HEIGHT = 36.f; ///< Space between the screen edge and the popup edge in the vertical dimension. -const float POPUP_TITLE_WIDTH = 648.0f; /// DEFAULT_BACKGROUND_BORDER( 17, 17, 13, 13 ); ///< Default border of the background. +const Rect DEFAULT_TITLE_PADDING( 20.0f, 20.0f, 20.0f, 20.0f ); ///< Title padding used on popups with content and/or controls (from Tizen GUI UX). +const Rect DEFAULT_TITLE_ONLY_PADDING( 8.0f, 8.0f, 8.0f, 8.0f ); ///< Title padding used on popups with a title only (like toast popups). +const Vector3 FOOTER_SIZE( 620.0f, 96.0f,0.0f ); ///< Default size of the bottom control area. +const float DEFAULT_RELATIVE_PARENT_WIDTH = 0.75f; ///< If width is not fixed, relative size to parent is used by default. + +} // Unnamed namespace -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Popup -/////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * Implementation. + */ Dali::Toolkit::Popup Popup::New() { - PopupStylePtr style = PopupStyleDefault::New(); - // Create the implementation - PopupPtr popup(new Popup(*style)); + PopupPtr popup( new Popup() ); - // Pass ownership to CustomActor via derived handle - Dali::Toolkit::Popup handle(*popup); + // Pass ownership to CustomActor via derived handle. + Dali::Toolkit::Popup handle( *popup ); - // Second-phase init of the implementation - // This can only be done after the CustomActor connection has been made... + // Second-phase initialisation of the implementation. + // This can only be done after the CustomActor connection has been made. popup->Initialize(); return handle; } -Popup::Popup(PopupStyle& style) -: Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ), - mShowing(false), - mState(Toolkit::Popup::POPUP_NONE), // Initially, the popup state should not be set, it's set in OnInitialize - mAlterAddedChild(false), - mPopupStyle(PopupStylePtr(&style)), - mPropertyTitle(Property::INVALID_INDEX), - mPropertyState(Property::INVALID_INDEX) +Popup::Popup() +: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ), + mTouchedOutsideSignal(), + mShowingSignal(), + mShownSignal(), + mHidingSignal(), + mHiddenSignal(), + mLayer(), + mPopupLayout(), + mBacking(), + mPreviousFocusedActor(), + mTailImage(), + mPopupContainer(), + mAnimation(), + mAlterAddedChild( false ), + mLayoutDirty( true ), + mAutoHideTimer(), + mTouchTransparent( false ), + mTitle(), + mContent(), + mFooter(), + mDisplayState( Toolkit::Popup::HIDDEN ), // Hidden until shown with SetDisplayState() + mTailVisible( false ), + mTailPosition( DEFAULT_TAIL_POSITION ), + mContextualMode( Toolkit::Popup::NON_CONTEXTUAL ), + mAnimationDuration( DEFAULT_POPUP_ANIMATION_DURATION ), + mAnimationMode( Toolkit::Popup::FADE ), + mEntryAnimationData(), + mExitAnimationData(), + mAutoHideDelay( 0 ), + mBackingEnabled( true ), + mBackingColor( DEFAULT_BACKING_COLOR ), + mPopupBackgroundImage(), + mBackgroundBorder( DEFAULT_BACKGROUND_BORDER ), + mMargin(), + mTailUpImage( DEFAULT_TAIL_UP_IMAGE_PATH ), + mTailDownImage( DEFAULT_TAIL_DOWN_IMAGE_PATH ), + mTailLeftImage( DEFAULT_TAIL_LEFT_IMAGE_PATH ), + mTailRightImage( DEFAULT_TAIL_RIGHT_IMAGE_PATH ) { SetKeyboardNavigationSupport( true ); } void Popup::OnInitialize() { - Dali::Stage stage = Dali::Stage::GetCurrent(); - Actor self = Self(); - self.SetSensitive(false); - // Reisize to fit the height of children - self.SetResizePolicy( ResizePolicy::FIT_TO_CHILDREN, Dimension::HEIGHT ); + self.SetName( "popup" ); + + // Apply some default resizing rules. + self.SetParentOrigin( ParentOrigin::CENTER ); + self.SetAnchorPoint( AnchorPoint::CENTER ); - // Create Layer + self.SetSizeModeFactor( DEFAULT_POPUP_PARENT_RELATIVE_SIZE ); + self.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH ); + self.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT ); + + // Create a new layer so all Popup components can appear above all other actors. mLayer = Layer::New(); - mLayer.SetName( "POPUP_LAYER" ); - mLayer.SetParentOrigin(ParentOrigin::CENTER); - mLayer.SetAnchorPoint(AnchorPoint::CENTER); + mLayer.SetName( "popupLayer" ); + + mLayer.SetParentOrigin( ParentOrigin::CENTER ); + mLayer.SetAnchorPoint( AnchorPoint::CENTER ); mLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); - mLayer.SetDrawMode( DrawMode::OVERLAY ); - // Any content after this point which is added to Self() will be reparented to - // mContent. - mAlterAddedChild = true; - // Add Backing (Dim effect) - CreateBacking(); - mAlterAddedChild = false; + // Important to set as invisible as otherwise, if the popup is parented, + // but not shown yet it will appear statically on the screen. + mLayer.SetVisible( false ); - // Add Dialog ( background image, title, content container, button container and tail ) - CreateDialog(); + // Add the layer to the hierarchy. + self.Add( mLayer ); + + // Add Backing (Dimmed effect). + mBacking = CreateBacking(); + mLayer.Add( mBacking ); - mLayer.Add( self ); + mPopupContainer = Actor::New(); + mPopupContainer.SetName( "popupContainer" ); + mPopupContainer.SetParentOrigin( ParentOrigin::CENTER ); + mPopupContainer.SetAnchorPoint( AnchorPoint::CENTER ); + mPopupContainer.SetResizePolicy( ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS ); + mLayer.Add( mPopupContainer ); + // Create the Popup layout to contain all main content. mPopupLayout = Toolkit::TableView::New( 3, 1 ); - mPopupLayout.SetName( "POPUP_LAYOUT_TABLE" ); - mPopupLayout.SetParentOrigin(ParentOrigin::CENTER); - mPopupLayout.SetAnchorPoint(AnchorPoint::CENTER); - mPopupLayout.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH ); + + // Adds the default background image. + SetPopupBackgroundImage( Toolkit::ImageView::New( DEFAULT_BACKGROUND_IMAGE_PATH ) ); + + mPopupLayout.SetName( "popupLayoutTable" ); + mPopupLayout.SetParentOrigin( ParentOrigin::CENTER ); + mPopupLayout.SetAnchorPoint( AnchorPoint::CENTER ); + + mPopupLayout.SetResizePolicy( ResizePolicy::USE_ASSIGNED_SIZE, Dimension::WIDTH ); mPopupLayout.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT ); - mPopupLayout.SetFitHeight( 0 ); // Set row to fit - mPopupLayout.SetFitHeight( 1 ); // Set row to fit - self.Add( mPopupLayout ); + mPopupLayout.SetSize( Stage::GetCurrent().GetSize().x * DEFAULT_RELATIVE_PARENT_WIDTH, 0.0f ); - // Any content after this point which is added to Self() will be reparented to - // mContent. - mAlterAddedChild = true; + mPopupLayout.SetFitHeight( 0 ); // Set row to fit. + mPopupLayout.SetFitHeight( 1 ); // Set row to fit. - // Default content. -// ShowTail(ParentOrigin::BOTTOM_CENTER); + mPopupLayout.TouchSignal().Connect( this, &Popup::OnDialogTouched ); - // Hide content by default. - SetState( Toolkit::Popup::POPUP_HIDE, 0.0f ); + mPopupContainer.Add( mPopupLayout ); - mPropertyTitle = self.RegisterProperty( PROPERTY_TITLE, "", Property::READ_WRITE ); - mPropertyState = self.RegisterProperty( PROPERTY_STATE, "POPUP_HIDE", Property::READ_WRITE ); + // Any content after this point which is added to Self() will be re-parented to mContent. + mAlterAddedChild = true; - // Make self as keyboard focusable and focus group - self.SetKeyboardFocusable(true); - SetAsKeyboardFocusGroup(true); + SetAsKeyboardFocusGroup( true ); } -void Popup::OnPropertySet( Property::Index index, Property::Value propertyValue ) +Popup::~Popup() { - if( index == mPropertyTitle ) - { - SetTitle(propertyValue.Get()); - } - else if ( index == mPropertyState ) + mEntryAnimationData.Clear(); + mExitAnimationData.Clear(); +} + +void Popup::LayoutAnimation() +{ + // Perform setup based on the currently selected animation. + switch( mAnimationMode ) { - std::string value( propertyValue.Get() ); - if(value == "POPUP_SHOW") + case Toolkit::Popup::ZOOM: { - SetState( Toolkit::Popup::POPUP_SHOW, 0.0f ); + // Zoom animations start fully zoomed out. + mPopupContainer.SetScale( Vector3::ZERO ); + break; } - else if( value == "POPUP_HIDE") + + case Toolkit::Popup::FADE: { - SetState( Toolkit::Popup::POPUP_HIDE, 0.0f ); + // Fade animations start transparent. + mPopupContainer.SetOpacity( 0.0f ); + break; } - } -} -Popup::~Popup() -{ - mLayer.Unparent(); -} + case Toolkit::Popup::CUSTOM: + { + // Initialise the custom animation by playing to the end of it's exit animation instantly. + // EG. If it was zooming in, then we zoom out fully instantly so the zoom in works. + StartTransitionAnimation( false, true ); + break; + } -size_t Popup::GetButtonCount() const -{ - return mButtons.size(); + case Toolkit::Popup::NONE: + { + break; + } + } } -void Popup::SetBackgroundImage( Actor image ) +void Popup::StartTransitionAnimation( bool transitionIn, bool instantaneous /* false */ ) { - // Removes any previous background. - if( mBackgroundImage && mPopupLayout ) + // Stop and recreate animation. + if ( mAnimation ) { - mPopupLayout.Remove( mBackgroundImage ); + mAnimation.Stop(); + mAnimation.Clear(); + mAnimation.Reset(); } + float duration = GetAnimationDuration(); - // Adds new background to the dialog. - mBackgroundImage = image; - - mBackgroundImage.SetName( "POPUP_BACKGROUND_IMAGE" ); - - // OnDialogTouched only consume the event. It prevents the touch event to be caught by the backing. - mBackgroundImage.TouchedSignal().Connect( this, &Popup::OnDialogTouched ); + // Setup variables ready to start the animations. + // If we are performing the animation instantaneously, we do not want to emit a signal. + if( !instantaneous ) + { + if( transitionIn ) + { + // Setup variables and signal that we are starting the transition. + // Note: We signal even if the transition is instant so signal order is consistent. + mShowingSignal.Emit(); + } + else + { + mHidingSignal.Emit(); + } + } - mBackgroundImage.SetResizePolicy( ResizePolicy::SIZE_FIXED_OFFSET_FROM_PARENT, Dimension::ALL_DIMENSIONS ); - mBackgroundImage.SetAnchorPoint( AnchorPoint::CENTER ); - mBackgroundImage.SetParentOrigin( ParentOrigin::CENTER ); + // Perform chosen animation for the Popup. + switch( mAnimationMode ) + { + case Toolkit::Popup::NONE: + { + mAnimation = Animation::New( 0.0f ); + break; + } - Vector3 border( mPopupStyle->backgroundOuterBorder.x, mPopupStyle->backgroundOuterBorder.z, 0.0f ); - mBackgroundImage.SetSizeModeFactor( border ); + case Toolkit::Popup::ZOOM: + { + mAnimation = Animation::New( duration ); + if( duration > Math::MACHINE_EPSILON_0 ) + { + if( transitionIn ) + { + mAnimation.AnimateTo( Property( mPopupContainer, Actor::Property::SCALE ), Vector3::ONE, AlphaFunction::EASE_IN_OUT, TimePeriod( duration * 0.25f, duration * 0.75f ) ); + } + else + { + // Zoom out animation is twice the speed. Modify the duration variable so the backing animation speed is modified also. + duration /= 2.0f; + mAnimation.SetDuration( duration ); + mAnimation.AnimateTo( Property( mPopupContainer, Actor::Property::SCALE ), Vector3::ZERO, AlphaFunction::EASE_IN_OUT, TimePeriod( 0.0f, duration ) ); + } + } + else + { + mPopupContainer.SetScale( transitionIn ? Vector3::ONE : Vector3::ZERO ); + } + break; + } - const bool prevAlter = mAlterAddedChild; - mAlterAddedChild = false; - Self().Add( mBackgroundImage ); - mAlterAddedChild = prevAlter; -} + case Toolkit::Popup::FADE: + { + mAnimation = Animation::New( duration ); + if( duration > Math::MACHINE_EPSILON_0 ) + { + if( transitionIn ) + { + mAnimation.AnimateTo( Property( mPopupContainer, Actor::Property::COLOR_ALPHA ), 1.0f, AlphaFunction::EASE_IN_OUT, TimePeriod( 0.30f, duration * 0.70f ) ); + } + else + { + mAnimation.AnimateTo( Property( mPopupContainer, Actor::Property::COLOR_ALPHA ), 0.0f, AlphaFunction::EASE_IN_OUT, TimePeriod( 0.0f, duration * 0.70f ) ); + } + } + else + { + mPopupContainer.SetOpacity( transitionIn ? 1.0f : 0.0f ); + } + break; + } -void Popup::SetButtonAreaImage( Actor image ) -{ - // Removes any previous area image. - if( mButtonAreaImage && mPopupLayout ) - { - mPopupLayout.Remove( mButtonAreaImage ); - } + case Toolkit::Popup::CUSTOM: + { + // Use a user specified animation for in and out. + // Read the correct animation depending on entry or exit. + // Attempt to use animation data defined from script data. + Dali::AnimationData* animationData = transitionIn ? &mEntryAnimationData : &mExitAnimationData; - // Adds new area image to the dialog. - mButtonAreaImage = image; + // Create a new animation from the pre-defined data in the AnimationData class. + // If there is no data, mAnimation is invalidated. + mAnimation = animationData->CreateAnimation( mPopupContainer, duration ); - // OnDialogTouched only consume the event. It prevents the touch event to be caught by the backing. - mButtonAreaImage.TouchedSignal().Connect( this, &Popup::OnDialogTouched ); + // If we don't have a valid animation, provide a blank one so play() can still function generically. + if( !mAnimation ) + { + // No animation was configured (even though custom mode was specified). Create a dummy animation to avoid an exception. + mAnimation = Animation::New( 0.0f ); + } - mButtonAreaImage.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); - mButtonAreaImage.SetAnchorPoint( AnchorPoint::CENTER ); - mButtonAreaImage.SetParentOrigin( ParentOrigin::CENTER ); + break; + } + } - if( GetButtonCount() > 0 ) + // Animate the backing, if enabled. + // This is set up last so that different animation modes can have an effect on the backing animation speed. + if( mBackingEnabled ) { - mBottomBg.Add( mButtonAreaImage ); + // Use the alpha from the user-specified color. + float targetAlpha = mBackingColor.a; + if( duration > Math::MACHINE_EPSILON_0 ) + { + if( transitionIn ) + { + mAnimation.AnimateTo( Property( mBacking, Actor::Property::COLOR_ALPHA ), targetAlpha, AlphaFunction::EASE_IN_OUT, TimePeriod( 0.0f, duration * 0.70f ) ); + } + else + { + mAnimation.AnimateTo( Property( mBacking, Actor::Property::COLOR_ALPHA ), 0.0f, AlphaFunction::EASE_IN_OUT, TimePeriod( 0.30f, duration * 0.70f ) ); + } + } + else + { + mBacking.SetProperty( Actor::Property::COLOR_ALPHA, transitionIn ? targetAlpha : 0.0f ); + } } -} -void Popup::SetTitle( const std::string& text ) -{ - // Replaces the current title actor. - if( mPopupLayout ) + // If we are performing the animation instantaneously, jump to the position directly and do not signal. + if( instantaneous ) + { + mAnimation.SetCurrentProgress( 1.0f ); + mAnimation.Play(); + } + else if( duration > Math::MACHINE_EPSILON_0 ) { - mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 0, 0 ) ); + // Run the animation. + mAnimation.FinishedSignal().Connect( this, &Popup::OnDisplayChangeAnimationFinished ); + mAnimation.Play(); } - - mTitle = Toolkit::TextLabel::New( text ); - mTitle.SetName( "POPUP_TITLE" ); - mTitle.SetProperty( Toolkit::TextLabel::Property::MULTI_LINE, true ); - mTitle.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" ); - - if( mPopupLayout ) + else { - mTitle.SetPadding( Padding( 0.0f, 0.0f, mPopupStyle->margin, mPopupStyle->margin ) ); - mTitle.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH ); - mTitle.SetResizePolicy( ResizePolicy::DIMENSION_DEPENDENCY, Dimension::HEIGHT ); - mPopupLayout.AddChild( mTitle, Toolkit::TableView::CellPosition( 0, 0 ) ); + // We did not use an animation to achive the transition. + // Trigger the state change directly. + DisplayStateChangeComplete(); } +} - RelayoutRequest(); +void Popup::OnDisplayChangeAnimationFinished( Animation& source ) +{ + DisplayStateChangeComplete(); } -std::string Popup::GetTitle() const +void Popup::DisplayStateChangeComplete() { - if( mTitle ) + // Remove contents from stage if completely hidden. + if( mDisplayState == Toolkit::Popup::HIDING ) { - return mTitle.GetProperty( Toolkit::TextLabel::Property::TEXT ); - } + mDisplayState = Toolkit::Popup::HIDDEN; - return std::string(); -} + mLayer.SetVisible( false ); + mPopupLayout.SetSensitive( false ); -void Popup::CreateFooter() -{ - if( !mBottomBg ) + // Guard against destruction during signal emission. + Toolkit::Popup handle( GetOwner() ); + mHiddenSignal.Emit(); + } + else if( mDisplayState == Toolkit::Popup::SHOWING ) { - // Adds bottom background - mBottomBg = Actor::New(); - mBottomBg.SetName( "POPUP_BOTTOM_BG" ); - mBottomBg.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); + mDisplayState = Toolkit::Popup::SHOWN; + Toolkit::Popup handle( GetOwner() ); + mShownSignal.Emit(); - mPopupLayout.SetFixedHeight( 2, mPopupStyle->bottomSize.height ); // Buttons - mPopupLayout.AddChild( mBottomBg, Toolkit::TableView::CellPosition( 2, 0 ) ); + // Start a timer to auto-hide if enabled. + if( mAutoHideDelay > 0u ) + { + mAutoHideTimer = Timer::New( mAutoHideDelay ); + mAutoHideTimer.TickSignal().Connect( this, &Popup::OnAutoHideTimeReached ); + mAutoHideTimer.Start(); + } } } -void Popup::AddButton( Toolkit::Button button ) +bool Popup::OnAutoHideTimeReached() { - mButtons.push_back( button ); - button.SetResizePolicy( ResizePolicy::USE_ASSIGNED_SIZE, Dimension::ALL_DIMENSIONS ); // Size will be assigned to it + // Display timer has expired, auto hide the popup exactly as if the user had clicked outside. + SetDisplayState( Toolkit::Popup::HIDDEN ); - // If this is the first button added - if( mButtons.size() == 1 ) + if( mAutoHideTimer ) { - CreateFooter(); + mAutoHideTimer.Stop(); + mAutoHideTimer.TickSignal().Disconnect( this, &Popup::OnAutoHideTimeReached ); + mAutoHideTimer.Reset(); + } + return true; +} - if( mButtonAreaImage ) +void Popup::SetPopupBackgroundImage( Actor image ) +{ + // Removes any previous background. + if( mPopupBackgroundImage ) + { + mPopupBackgroundImage.Unparent(); + if( mTailImage ) { - mBottomBg.Add( mButtonAreaImage ); + mTailImage.Unparent(); } } - mBottomBg.Add( button ); + // Adds new background to the dialog. + mPopupBackgroundImage = image; + mPopupBackgroundImage.SetName( "popupBackgroundImage" ); + mPopupBackgroundImage.SetAnchorPoint( AnchorPoint::CENTER ); + mPopupBackgroundImage.SetParentOrigin( ParentOrigin::CENTER ); - RelayoutRequest(); -} + // OnDialogTouched only consumes the event. It prevents the touch event to be caught by the backing. + mPopupBackgroundImage.TouchSignal().Connect( this, &Popup::OnDialogTouched ); -void Popup::SetState( Toolkit::Popup::PopupState state ) -{ - SetState( state, POPUP_ANIMATION_DURATION ); -} + // Set the popup border to be slightly larger than the layout contents. + UpdateBackgroundPositionAndSize(); -void Popup::SetState( Toolkit::Popup::PopupState state, float duration ) -{ - // default animation behaviour. - HandleStateChange(state, duration); + const bool prevAlter = mAlterAddedChild; + mAlterAddedChild = false; + mPopupContainer.Add( mPopupBackgroundImage ); + mAlterAddedChild = prevAlter; + + if( mTailImage ) + { + mPopupBackgroundImage.Add( mTailImage ); + } + + mLayoutDirty = true; } -Toolkit::Popup::PopupState Popup::GetState() const +Actor Popup::GetPopupBackgroundImage() const { - return mState; + return mPopupBackgroundImage; } -void Popup::ShowTail(const Vector3& position) +void Popup::SetTitle( Actor titleActor ) { - // Replaces the tail actor. - if(mTailImage && mTailImage.GetParent()) + // Replaces the current title actor. + if( !mPopupLayout ) { - mTailImage.GetParent().Remove( mTailImage ); - mTailImage.Reset(); + return; } - std::string image = ""; - - // depending on position of tail around ParentOrigin, a different tail image is used... - if(position.y < Math::MACHINE_EPSILON_1) - { - image = mPopupStyle->tailUpImage; - } - else if(position.y > 1.0f - Math::MACHINE_EPSILON_1) - { - image = mPopupStyle->tailDownImage; - } - else if(position.x < Math::MACHINE_EPSILON_1) - { - image = mPopupStyle->tailLeftImage; - } - else if(position.x > 1.0f - Math::MACHINE_EPSILON_1) + if( mTitle ) { - image = mPopupStyle->tailRightImage; + mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 0, 0) ); } + mTitle = titleActor; - if(image != "") + if( mTitle ) { - Image tail = ResourceImage::New( image ); - mTailImage = ImageActor::New(tail); - const Vector3 anchorPoint = AnchorPoint::BOTTOM_RIGHT - position; - - mTailImage.SetParentOrigin(position); - mTailImage.SetAnchorPoint(anchorPoint); + // Set up padding to give sensible default behaviour + // (an application developer can later override this if they wish). + mTitle.SetPadding( DEFAULT_TITLE_PADDING ); - CreateFooter(); - - mBottomBg.Add(mTailImage); + mPopupLayout.AddChild( mTitle, Toolkit::TableView::CellPosition( 0, 0 ) ); } + + mLayoutDirty = true; + RelayoutRequest(); } -void Popup::HideTail() +Actor Popup::GetTitle() const { - ShowTail(ParentOrigin::CENTER); + return mTitle; } -void Popup::SetStyle(PopupStyle& style) +void Popup::SetContent( Actor content ) { - mPopupStyle = PopupStylePtr(&style); - // update // + // Remove previous content actor. + if( mPopupLayout ) + { + mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 1, 0 ) ); + } + // Keep a handle to the new content. + mContent = content; + + if( mContent ) + { + mContent.SetName( "popupContent" ); + + mPopupLayout.AddChild( mContent, Toolkit::TableView::CellPosition( 1, 0 ) ); + } + + mLayoutDirty = true; + RelayoutRequest(); } -PopupStylePtr Popup::GetStyle() const +Actor Popup::GetContent() const { - return mPopupStyle; + return mContent; } -void Popup::SetDefaultBackgroundImage() +void Popup::SetFooter( Actor footer ) { - Image buttonBg = ResourceImage::New( mPopupStyle->buttonAreaImage ); - ImageActor buttonBgImage = ImageActor::New( buttonBg ); - buttonBgImage.SetStyle( ImageActor::STYLE_NINE_PATCH ); - buttonBgImage.SetNinePatchBorder( mPopupStyle->buttonArea9PatchBorder ); + // Remove previous content actor. + if( mPopupLayout ) + { + mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 2, 0 ) ); + } - SetBackgroundImage( ImageActor::New( ResourceImage::New( mPopupStyle->backgroundImage ) ) ); - SetButtonAreaImage( buttonBgImage ); -} + // Keep a handle to the new content. + mFooter = footer; -void Popup::CreateBacking() -{ - mBacking = Dali::Toolkit::CreateSolidColorActor( mPopupStyle->backingColor ); - mBacking.SetName( "POPUP_BACKING" ); + if( mFooter ) + { + mFooter.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH ); - mBacking.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); - mBacking.SetSensitive(true); + // The control container has a fixed height. + mPopupLayout.SetFitHeight( 2u ); + mPopupLayout.AddChild( footer, Toolkit::TableView::CellPosition( 2, 0 ) ); + } - mLayer.Add( mBacking ); - mBacking.SetOpacity(0.0f); - mBacking.TouchedSignal().Connect( this, &Popup::OnBackingTouched ); - mBacking.MouseWheelEventSignal().Connect(this, &Popup::OnBackingMouseWheelEvent); + mLayoutDirty = true; + RelayoutRequest(); } -void Popup::CreateDialog() +Actor Popup::GetFooter() const { - // Adds default background image. - SetDefaultBackgroundImage(); + return mFooter; } -void Popup::HandleStateChange( Toolkit::Popup::PopupState state, float duration ) +void Popup::SetDisplayState( Toolkit::Popup::DisplayState displayState ) { - Vector3 targetSize; - float targetBackingAlpha; + // Convert the 4-way state to a bool, true for show, false for hide. + bool display = ( displayState == Toolkit::Popup::SHOWING ) || ( displayState == Toolkit::Popup::SHOWN ); - if(mState == state) + // Ignore if we are already at the target display state. + if( display == ( ( mDisplayState == Toolkit::Popup::SHOWING ) || ( mDisplayState == Toolkit::Popup::SHOWN ) ) ) { return; } - mState = state; - switch(state) + + // Convert the bool state to the actual display state to use. + mDisplayState = display ? Toolkit::Popup::SHOWING : Toolkit::Popup::HIDING; + + if ( display ) { - case Toolkit::Popup::POPUP_HIDE: - { - targetSize = Vector3(0.0f, 0.0f, 1.0f); - targetBackingAlpha = 0.0f; - mShowing = false; - ClearKeyInputFocus(); + // Update the state to indicate the current intent. + mDisplayState = Toolkit::Popup::SHOWING; - // Retore the keyboard focus when popup is hidden - if(mPreviousFocusedActor && mPreviousFocusedActor.IsKeyboardFocusable() ) - { - Dali::Toolkit::KeyboardFocusManager keyboardFocusManager = Dali::Toolkit::KeyboardFocusManager::Get(); - if( keyboardFocusManager ) - { - keyboardFocusManager.SetCurrentFocusActor(mPreviousFocusedActor); - } - } + // We want the popup to have key input focus when it is displayed + SetKeyInputFocus(); - break; - } + // We are displaying so bring the popup layer to the front, and set it visible so it is rendered. + mLayer.RaiseToTop(); + mLayer.SetVisible( true ); - case Toolkit::Popup::POPUP_SHOW: - default: + // Set up the layout if this is the first display or the layout has become dirty. + if( mLayoutDirty ) { - targetSize = Vector3(1.0f, 1.0f, 1.0f); - targetBackingAlpha = 1.0f; - mShowing = true; + // Bake-in any style and layout options to create the Popup layout. + LayoutPopup(); + } - // Add contents to stage for showing. - if( !mLayer.GetParent() ) - { - Dali::Stage stage = Dali::Stage::GetCurrent(); - stage.Add( mLayer ); - mLayer.RaiseToTop(); - } + // Allow the popup to catch events. + mPopupLayout.SetSensitive( true ); - Self().SetSensitive(true); - SetKeyInputFocus(); + // Handle the keyboard focus when popup is shown. + Dali::Toolkit::KeyboardFocusManager keyboardFocusManager = Dali::Toolkit::KeyboardFocusManager::Get(); + if( keyboardFocusManager ) + { + mPreviousFocusedActor = keyboardFocusManager.GetCurrentFocusActor(); - // Handle the keyboard focus when popup is shown - Dali::Toolkit::KeyboardFocusManager keyboardFocusManager = Dali::Toolkit::KeyboardFocusManager::Get(); - if( keyboardFocusManager ) + if( Self().IsKeyboardFocusable() ) { - mPreviousFocusedActor = keyboardFocusManager.GetCurrentFocusActor(); - + // Setup the actgor to start focus from. + Actor focusActor; if( mContent && mContent.IsKeyboardFocusable() ) { - // If content is focusable, move the focus to content - keyboardFocusManager.SetCurrentFocusActor(mContent); + // If the content is focusable, move the focus to the content. + focusActor = mContent; } - else if( !mButtons.empty() ) + else if( mFooter && mFooter.IsKeyboardFocusable() ) { - // Otherwise, movethe focus to the first button - keyboardFocusManager.SetCurrentFocusActor(mButtons[0]); + // If the footer is focusable, move the focus to the footer. + focusActor = mFooter; } else { - DALI_LOG_WARNING("There is no focusable in popup\n"); + DALI_LOG_WARNING( "There is no focusable in popup\n" ); + } + + if( focusActor ) + { + keyboardFocusManager.SetCurrentFocusActor( focusActor ); } } - break; } } - - Actor self = Self(); - if(duration > Math::MACHINE_EPSILON_1) + else // Not visible. { - if ( mAnimation ) + mDisplayState = Toolkit::Popup::HIDING; + ClearKeyInputFocus(); + + // Restore the keyboard focus when popup is hidden. + if( mPreviousFocusedActor && mPreviousFocusedActor.IsKeyboardFocusable() ) { - mAnimation.Stop(); - mAnimation.Clear(); - mAnimation.Reset(); + Dali::Toolkit::KeyboardFocusManager keyboardFocusManager = Dali::Toolkit::KeyboardFocusManager::Get(); + if( keyboardFocusManager ) + { + keyboardFocusManager.SetCurrentFocusActor( mPreviousFocusedActor ); + } } - mAnimation = Animation::New(duration); + } + + // Perform animation. + StartTransitionAnimation( display ); +} + +Toolkit::Popup::DisplayState Popup::GetDisplayState() const +{ + return mDisplayState; +} - if(mShowing) +void Popup::LayoutPopup() +{ + mLayoutDirty = false; + + /* When animating in, we want to respect the origin applied to Self(). + * For example, if zooming, not only will the final result be anchored to the + * selected point, but the zoom will originate from this point also. + * + * EG: ParentOrigin::TOP_LEFT, AnchorPoint::TOP_LEFT : + * + * -------- -------- + * |X| |XXX| + * |`` Animates |XXX| + * | to: |XXX| + * | |```` + * | | + */ + mPopupContainer.SetParentOrigin( Self().GetCurrentParentOrigin() ); + mPopupContainer.SetAnchorPoint( Self().GetCurrentAnchorPoint() ); + + // If there is only a title, use less padding. + if( mTitle ) + { + if( !mContent && !mFooter ) { - mAnimation.AnimateTo( Property(mBacking, Actor::Property::COLOR_ALPHA), targetBackingAlpha, AlphaFunctions::EaseInOut, TimePeriod(0.0f, duration * 0.5f) ); - mAnimation.AnimateTo( Property(self, Actor::Property::SCALE), targetSize, AlphaFunctions::EaseInOut, TimePeriod(duration * 0.5f, duration * 0.5f) ); + mTitle.SetPadding( DEFAULT_TITLE_ONLY_PADDING ); } else { - mAnimation.AnimateTo( Property(mBacking, Actor::Property::COLOR_ALPHA), targetBackingAlpha, AlphaFunctions::EaseInOut, TimePeriod(0.0f, duration * 0.5f) ); - mAnimation.AnimateTo( Property(self, Actor::Property::SCALE), targetSize, AlphaFunctions::EaseInOut, TimePeriod(0.0f, duration * 0.5f) ); + mTitle.SetPadding( DEFAULT_TITLE_PADDING ); } - mAnimation.Play(); - mAnimation.FinishedSignal().Connect(this, &Popup::OnStateAnimationFinished); } - else - { - mBacking.SetOpacity( targetBackingAlpha ); - self.SetScale( targetSize ); - HandleStateChangeComplete(); - } + // Allow derived classes to perform any layout they may need to do. + OnLayoutSetup(); + + // Update background visibility. + mPopupContainer.SetVisible( !( !mFooter && mPopupLayout.GetChildCount() == 0 ) ); + + // Create / destroy / position the tail as needed. + LayoutTail(); + + // Setup any layout and initialisation required for the selected animation. + LayoutAnimation(); + + RelayoutRequest(); } -void Popup::HandleStateChangeComplete() +void Popup::LayoutTail() { - // Remove contents from stage if completely hidden. - if( ( mState == Toolkit::Popup::POPUP_HIDE ) && mLayer.GetParent() ) + // Removes the tail actor. + if( mTailImage && mTailImage.GetParent() ) { - mLayer.Unparent(); - Self().SetSensitive( false ); - - // Guard against destruction during signal emission - Toolkit::Popup handle( GetOwner() ); - mHiddenSignal.Emit(); + mTailImage.GetParent().Remove( mTailImage ); + mTailImage.Reset(); + } + + if( !mTailVisible ) + { + return; + } + + const Vector3& parentOrigin = GetTailPosition(); + Vector3 position; + std::string image; + Vector3 anchorPoint; + + // depending on position of tail around ParentOrigin, a different tail image is used... + if( parentOrigin.y < Math::MACHINE_EPSILON_1 ) + { + image = mTailUpImage; + anchorPoint = AnchorPoint::BOTTOM_CENTER; + position.y = mBackgroundBorder.top; + } + else if( parentOrigin.y > ( 1.0f - Math::MACHINE_EPSILON_1 ) ) + { + image = mTailDownImage; + anchorPoint = AnchorPoint::TOP_CENTER; + position.y = - mBackgroundBorder.bottom; + } + else if( parentOrigin.x < Math::MACHINE_EPSILON_1 ) + { + image = mTailLeftImage; + anchorPoint = AnchorPoint::CENTER_RIGHT; + position.x = mBackgroundBorder.left; } + else if( parentOrigin.x > ( 1.0f - Math::MACHINE_EPSILON_1 ) ) + { + image = mTailRightImage; + anchorPoint = AnchorPoint::CENTER_LEFT; + position.x = - mBackgroundBorder.right; + } + + if( !image.empty() ) + { + // Adds the tail actor. + mTailImage = Toolkit::ImageView::New( image ); + mTailImage.SetName( "tailImage" ); + mTailImage.SetParentOrigin( parentOrigin ); + mTailImage.SetAnchorPoint( anchorPoint ); + mTailImage.SetPosition( position ); + + if( mPopupBackgroundImage ) + { + mPopupBackgroundImage.Add( mTailImage ); + } + } +} + +void Popup::SetContextualMode( Toolkit::Popup::ContextualMode mode ) +{ + mContextualMode = mode; + mLayoutDirty = true; +} + +Toolkit::Popup::ContextualMode Popup::GetContextualMode() const +{ + return mContextualMode; +} + +Toolkit::Control Popup::CreateBacking() +{ + Toolkit::Control backing = Control::New(); + backing.SetProperty( Toolkit::Control::Property::BACKGROUND, + Property::Map().Add( Toolkit::Visual::Property::TYPE, Toolkit::Visual::COLOR ) + .Add( Toolkit::ColorVisual::Property::MIX_COLOR, Vector4( mBackingColor.r, mBackingColor.g, mBackingColor.b, 1.0f ) ) ); + backing.SetName( "popupBacking" ); + + // Must always be positioned top-left of stage, regardless of parent. + backing.SetInheritPosition(false); + backing.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + + // Always the full size of the stage. + backing.SetResizePolicy( ResizePolicy::FIXED, Dimension::ALL_DIMENSIONS ); + backing.SetSize( Stage::GetCurrent().GetSize() ); + + // Catch events. + backing.SetSensitive( true ); + + // Default to being transparent. + backing.SetProperty( Actor::Property::COLOR_ALPHA, 0.0f ); + backing.TouchSignal().Connect( this, &Popup::OnBackingTouched ); + backing.WheelEventSignal().Connect( this, &Popup::OnBackingWheelEvent ); + return backing; } Toolkit::Popup::TouchedOutsideSignalType& Popup::OutsideTouchedSignal() @@ -558,11 +938,554 @@ Toolkit::Popup::TouchedOutsideSignalType& Popup::OutsideTouchedSignal() return mTouchedOutsideSignal; } -Toolkit::Popup::HiddenSignalType& Popup::HiddenSignal() +Toolkit::Popup::DisplayStateChangeSignalType& Popup::ShowingSignal() +{ + return mShowingSignal; +} + +Toolkit::Popup::DisplayStateChangeSignalType& Popup::ShownSignal() +{ + return mShownSignal; +} + +Toolkit::Popup::DisplayStateChangeSignalType& Popup::HidingSignal() +{ + return mHidingSignal; +} + +Toolkit::Popup::DisplayStateChangeSignalType& Popup::HiddenSignal() { return mHiddenSignal; } +void Popup::SetTailVisibility( bool visible ) +{ + mTailVisible = visible; + mLayoutDirty = true; +} + +const bool Popup::IsTailVisible() const +{ + return mTailVisible; +} + +void Popup::SetTailPosition( Vector3 position ) +{ + mTailPosition = position; + mLayoutDirty = true; +} + +const Vector3& Popup::GetTailPosition() const +{ + return mTailPosition; +} + +void Popup::SetAnimationDuration( float duration ) +{ + mAnimationDuration = duration; + mLayoutDirty = true; +} + +float Popup::GetAnimationDuration() const +{ + return mAnimationDuration; +} + +void Popup::SetAnimationMode( Toolkit::Popup::AnimationMode animationMode ) +{ + mAnimationMode = animationMode; + mLayoutDirty = true; +} + +Toolkit::Popup::AnimationMode Popup::GetAnimationMode() const +{ + return mAnimationMode; +} + +void Popup::SetEntryAnimationData( const Property::Map& map ) +{ + mEntryAnimationData.Clear(); + Scripting::NewAnimation( map, mEntryAnimationData ); +} + +void Popup::SetExitAnimationData( const Property::Map& map ) +{ + mExitAnimationData.Clear(); + Scripting::NewAnimation( map, mExitAnimationData ); +} + +void Popup::UpdateBackgroundPositionAndSize() +{ + if( mPopupBackgroundImage ) + { + mPopupBackgroundImage.SetResizePolicy( ResizePolicy::SIZE_FIXED_OFFSET_FROM_PARENT, Dimension::ALL_DIMENSIONS ); + mPopupBackgroundImage.SetSizeModeFactor( Vector3( mBackgroundBorder.left + mBackgroundBorder.right, mBackgroundBorder.top + mBackgroundBorder.bottom, 0.0f ) ); + + // Adjust the position of the background so the transparent areas are set appropriately + mPopupBackgroundImage.SetPosition( ( mBackgroundBorder.right - mBackgroundBorder.left ) * 0.5f, ( mBackgroundBorder.bottom - mBackgroundBorder.top ) * 0.5f ); + } +} + +void Popup::SetAutoHideDelay( int delay ) +{ + mAutoHideDelay = delay; +} + +int Popup::GetAutoHideDelay() const +{ + return mAutoHideDelay; +} + +void Popup::SetBackingEnabled( bool enabled ) +{ + mBackingEnabled = enabled; + mLayoutDirty = true; +} + +const bool Popup::IsBackingEnabled() const +{ + return mBackingEnabled; +} + +void Popup::SetBackingColor( Vector4 color ) +{ + mBackingColor = color; + mBacking.SetBackgroundColor( Vector4( color.r, color.g, color.b, 1.0f ) ); + mLayoutDirty = true; +} + +const Vector4& Popup::GetBackingColor() const +{ + return mBackingColor; +} + +void Popup::SetTailUpImage( std::string image ) +{ + mTailUpImage = image; + mLayoutDirty = true; + LayoutTail(); +} + +const std::string& Popup::GetTailUpImage() const +{ + return mTailUpImage; +} + +void Popup::SetTailDownImage( std::string image ) +{ + mTailDownImage = image; + mLayoutDirty = true; + LayoutTail(); +} + +const std::string& Popup::GetTailDownImage() const +{ + return mTailDownImage; +} + +void Popup::SetTailLeftImage( std::string image ) +{ + mTailLeftImage = image; + mLayoutDirty = true; + LayoutTail(); +} + +const std::string& Popup::GetTailLeftImage() const +{ + return mTailLeftImage; +} + +void Popup::SetTailRightImage( std::string image ) +{ + mTailRightImage = image; + mLayoutDirty = true; + LayoutTail(); +} + +const std::string& Popup::GetTailRightImage() const +{ + return mTailRightImage; +} + +void Popup::SetTouchTransparent( bool enabled ) +{ + mTouchTransparent = enabled; +} + +const bool Popup::IsTouchTransparent() const +{ + return mTouchTransparent; +} + +void Popup::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value ) +{ + Toolkit::Popup popup = Toolkit::Popup::DownCast( Dali::BaseHandle( object ) ); + + if ( popup ) + { + Popup& popupImpl( GetImpl( popup ) ); + + switch ( propertyIndex ) + { + case Toolkit::Popup::Property::TITLE: + { + Property::Map valueMap; + if( value.Get( valueMap ) ) + { + popupImpl.SetTitle( Scripting::NewActor( valueMap ) ); + } + break; + } + case Toolkit::Popup::Property::CONTENT: + { + Property::Map valueMap; + if( value.Get( valueMap ) ) + { + popupImpl.SetContent( Scripting::NewActor( valueMap ) ); + } + break; + } + case Toolkit::Popup::Property::FOOTER: + { + Property::Map valueMap; + if( value.Get( valueMap ) ) + { + popupImpl.SetFooter( Scripting::NewActor( valueMap ) ); + } + break; + } + case Toolkit::Popup::Property::DISPLAY_STATE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + Toolkit::Popup::DisplayState displayState( Toolkit::Popup::HIDDEN ); + if( Scripting::GetEnumeration< Toolkit::Popup::DisplayState >( valueString.c_str(), DisplayStateTable, DisplayStateTableCount, displayState ) ) + { + popupImpl.SetDisplayState( displayState ); + } + } + break; + } + case Toolkit::Popup::Property::TOUCH_TRANSPARENT: + { + bool valueBool; + if( value.Get( valueBool ) ) + { + popupImpl.SetTouchTransparent( valueBool ); + } + break; + } + case Toolkit::Popup::Property::TAIL_VISIBILITY: + { + bool valueBool; + if( value.Get( valueBool ) ) + { + popupImpl.SetTailVisibility( valueBool ); + } + break; + } + case Toolkit::Popup::Property::TAIL_POSITION: + { + Vector3 valueVector3; + if( value.Get( valueVector3 ) ) + { + popupImpl.SetTailPosition( valueVector3 ); + } + break; + } + case Toolkit::Popup::Property::CONTEXTUAL_MODE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + Toolkit::Popup::ContextualMode contextualMode( Toolkit::Popup::BELOW ); + if( Scripting::GetEnumeration< Toolkit::Popup::ContextualMode >( valueString.c_str(), ContextualModeTable, ContextualModeTableCount, contextualMode ) ) + { + popupImpl.SetContextualMode( contextualMode ); + } + } + break; + } + case Toolkit::Popup::Property::ANIMATION_DURATION: + { + float valueFloat; + if( value.Get( valueFloat ) ) + { + popupImpl.SetAnimationDuration( valueFloat ); + } + break; + } + case Toolkit::Popup::Property::ANIMATION_MODE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + Toolkit::Popup::AnimationMode animationMode( Toolkit::Popup::FADE ); + if( Scripting::GetEnumeration< Toolkit::Popup::AnimationMode >( valueString.c_str(), AnimationModeTable, AnimationModeTableCount, animationMode ) ) + { + popupImpl.SetAnimationMode( animationMode ); + } + } + break; + } + case Toolkit::Popup::Property::ENTRY_ANIMATION: + { + Property::Map valueMap; + if( value.Get( valueMap ) ) + { + popupImpl.SetEntryAnimationData( valueMap ); + } + break; + } + case Toolkit::Popup::Property::EXIT_ANIMATION: + { + Property::Map valueMap; + if( value.Get( valueMap ) ) + { + popupImpl.SetExitAnimationData( valueMap ); + } + break; + } + case Toolkit::Popup::Property::AUTO_HIDE_DELAY: + { + int valueInt; + if( value.Get( valueInt ) ) + { + popupImpl.SetAutoHideDelay( valueInt ); + } + break; + } + case Toolkit::Popup::Property::BACKING_ENABLED: + { + bool valueBool; + if( value.Get( valueBool ) ) + { + popupImpl.SetBackingEnabled( valueBool ); + } + break; + } + case Toolkit::Popup::Property::BACKING_COLOR: + { + Vector4 valueVector4; + if( value.Get( valueVector4 ) ) + { + popupImpl.SetBackingColor( valueVector4 ); + } + break; + } + case Toolkit::Popup::Property::POPUP_BACKGROUND_IMAGE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + Toolkit::ImageView actor = Toolkit::ImageView::New( valueString ); + popupImpl.SetPopupBackgroundImage( actor ); + } + break; + } + case Toolkit::Popup::Property::POPUP_BACKGROUND_BORDER: + { + bool valueUpdated = false; + + Vector4 valueVector4; + if( value.Get( popupImpl.mBackgroundBorder ) ) + { + valueUpdated = true; + } + else if( value.Get( valueVector4 ) ) + { + popupImpl.mBackgroundBorder.left = valueVector4.x; + popupImpl.mBackgroundBorder.right = valueVector4.y; + popupImpl.mBackgroundBorder.bottom = valueVector4.z; + popupImpl.mBackgroundBorder.top = valueVector4.w; + valueUpdated = true; + } + + if( valueUpdated ) + { + popupImpl.LayoutTail(); // Update the tail if required + popupImpl.UpdateBackgroundPositionAndSize(); // Update the background's size and position + } + break; + } + case Toolkit::Popup::Property::TAIL_UP_IMAGE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + popupImpl.SetTailUpImage( valueString ); + } + break; + } + case Toolkit::Popup::Property::TAIL_DOWN_IMAGE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + popupImpl.SetTailDownImage( valueString ); + } + break; + } + case Toolkit::Popup::Property::TAIL_LEFT_IMAGE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + popupImpl.SetTailLeftImage( valueString ); + } + break; + } + case Toolkit::Popup::Property::TAIL_RIGHT_IMAGE: + { + std::string valueString; + if( value.Get( valueString ) ) + { + popupImpl.SetTailRightImage( valueString ); + } + break; + } + } + } +} + +Property::Value Popup::GetProperty( BaseObject* object, Property::Index propertyIndex ) +{ + Property::Value value; + + Toolkit::Popup popup = Toolkit::Popup::DownCast( Dali::BaseHandle( object ) ); + + if ( popup ) + { + Popup& popupImpl( GetImpl( popup ) ); + + switch ( propertyIndex ) + { + case Toolkit::Popup::Property::TITLE: + { + Property::Map map; + Scripting::CreatePropertyMap( popupImpl.GetTitle(), map ); + value = map; + break; + } + case Toolkit::Popup::Property::CONTENT: + { + Property::Map map; + Scripting::CreatePropertyMap( popupImpl.GetContent(), map ); + value = map; + break; + } + case Toolkit::Popup::Property::FOOTER: + { + Property::Map map; + Scripting::CreatePropertyMap( popupImpl.GetFooter(), map ); + value = map; + break; + } + case Toolkit::Popup::Property::DISPLAY_STATE: + { + value = Scripting::GetLinearEnumerationName< Toolkit::Popup::DisplayState >( popupImpl.GetDisplayState(), DisplayStateTable, DisplayStateTableCount ); + break; + } + case Toolkit::Popup::Property::TOUCH_TRANSPARENT: + { + value = popupImpl.IsTouchTransparent(); + break; + } + case Toolkit::Popup::Property::TAIL_VISIBILITY: + { + value = popupImpl.IsTailVisible(); + break; + } + case Toolkit::Popup::Property::TAIL_POSITION: + { + value = popupImpl.GetTailPosition(); + break; + } + case Toolkit::Popup::Property::CONTEXTUAL_MODE: + { + value = Scripting::GetLinearEnumerationName< Toolkit::Popup::ContextualMode >( popupImpl.GetContextualMode(), ContextualModeTable, ContextualModeTableCount ); + break; + } + case Toolkit::Popup::Property::ANIMATION_DURATION: + { + value = popupImpl.GetAnimationDuration(); + break; + } + case Toolkit::Popup::Property::ANIMATION_MODE: + { + value = Scripting::GetLinearEnumerationName< Toolkit::Popup::AnimationMode >( popupImpl.GetAnimationMode(), AnimationModeTable, AnimationModeTableCount ); + break; + } + case Toolkit::Popup::Property::ENTRY_ANIMATION: + { + // Note: Cannot retrieve property map from animation. + Property::Map map; + value = map; + break; + } + case Toolkit::Popup::Property::EXIT_ANIMATION: + { + // Note: Cannot retrieve property map from animation. + Property::Map map; + value = map; + break; + } + case Toolkit::Popup::Property::AUTO_HIDE_DELAY: + { + value = popupImpl.GetAutoHideDelay(); + break; + } + case Toolkit::Popup::Property::BACKING_ENABLED: + { + value = popupImpl.IsBackingEnabled(); + break; + } + case Toolkit::Popup::Property::BACKING_COLOR: + { + value = popupImpl.GetBackingColor(); + break; + } + case Toolkit::Popup::Property::POPUP_BACKGROUND_IMAGE: + { + Toolkit::ImageView imageView = Toolkit::ImageView::DownCast( popupImpl.GetPopupBackgroundImage() ); + if( imageView ) + { + value = imageView.GetProperty( Toolkit::ImageView::Property::IMAGE ); + } + break; + } + case Toolkit::Popup::Property::POPUP_BACKGROUND_BORDER: + { + value = popupImpl.mBackgroundBorder; + break; + } + case Toolkit::Popup::Property::TAIL_UP_IMAGE: + { + value = popupImpl.GetTailUpImage(); + break; + } + case Toolkit::Popup::Property::TAIL_DOWN_IMAGE: + { + value = popupImpl.GetTailDownImage(); + break; + } + case Toolkit::Popup::Property::TAIL_LEFT_IMAGE: + { + value = popupImpl.GetTailLeftImage(); + break; + } + case Toolkit::Popup::Property::TAIL_RIGHT_IMAGE: + { + value = popupImpl.GetTailRightImage(); + break; + } + } + } + + return value; +} + bool Popup::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor ) { Dali::BaseHandle handle( object ); @@ -574,6 +1497,18 @@ bool Popup::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tra { popup.OutsideTouchedSignal().Connect( tracker, functor ); } + else if( 0 == strcmp( signalName.c_str(), SIGNAL_SHOWING ) ) + { + popup.ShowingSignal().Connect( tracker, functor ); + } + else if( 0 == strcmp( signalName.c_str(), SIGNAL_SHOWN ) ) + { + popup.ShownSignal().Connect( tracker, functor ); + } + else if( 0 == strcmp( signalName.c_str(), SIGNAL_HIDING ) ) + { + popup.HidingSignal().Connect( tracker, functor ); + } else if( 0 == strcmp( signalName.c_str(), SIGNAL_HIDDEN ) ) { popup.HiddenSignal().Connect( tracker, functor ); @@ -587,355 +1522,442 @@ bool Popup::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tra return connected; } -void Popup::OnStateAnimationFinished( Animation& source ) +bool Popup::OnBackingTouched( Actor actor, const TouchData& touch ) { - HandleStateChangeComplete(); -} - -bool Popup::OnBackingTouched(Actor actor, const TouchEvent& event) -{ - if(event.GetPointCount()>0) + // Allow events to pass through if touch transparency is enabled. + if( mTouchTransparent ) { - const TouchPoint& point = event.GetPoint(0); + return false; + } - if(point.state == TouchPoint::Down) + if( touch.GetPointCount() > 0 ) + { + if( touch.GetState( 0 ) == PointState::DOWN ) { - // Guard against destruction during signal emission + // Guard against destruction during signal emission. Toolkit::Popup handle( GetOwner() ); mTouchedOutsideSignal.Emit(); } } + // Block anything behind backing becoming touched. + mLayer.SetTouchConsumed( true ); return true; } -bool Popup::OnBackingMouseWheelEvent(Actor actor, const MouseWheelEvent& event) +bool Popup::OnBackingWheelEvent( Actor actor, const WheelEvent& event ) { - // consume mouse wheel event in dimmed backing actor + // Allow events to pass through if touch transparency is enabled. + if( mTouchTransparent ) + { + return false; + } + + // Consume wheel event in dimmed backing actor. + mLayer.SetTouchConsumed( true ); return true; } -bool Popup::OnDialogTouched(Actor actor, const TouchEvent& event) +bool Popup::OnDialogTouched( Actor actor, const TouchData& touch ) { - // consume event (stops backing actor receiving touch events) + // Allow events to pass through if touch transparency is enabled. + if( mTouchTransparent ) + { + return false; + } + + // Consume event (stops backing actor receiving touch events) + mLayer.SetTouchConsumed( true ); return true; } -void Popup::OnControlChildAdd( Actor& child ) +void Popup::OnStageConnection( int depth ) { - // reparent any children added by user to the body layer. - if( mAlterAddedChild ) - { - // Removes previously added content. - if( mContent ) - { - mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 1, 0 ) ); - } + Control::OnStageConnection( depth ); - // keep a handle to the new content. - mContent = child; - - mPopupLayout.AddChild( mContent, Toolkit::TableView::CellPosition( 1, 0 ) ); - } + mLayoutDirty = true; + RelayoutRequest(); } -void Popup::OnRelayout( const Vector2& size, RelayoutContainer& container ) +void Popup::OnChildAdd( Actor& child ) { - // Hide the background image - mBackgroundImage.SetVisible( !( mButtons.empty() && mPopupLayout.GetChildCount() == 0 ) ); + Control::OnChildAdd( child ); - // Relayout All buttons - if ( !mButtons.empty() ) + // Re-parent any children added by user to the body layer. + if( mAlterAddedChild ) { - // All buttons should be the same size and fill the button area. The button spacing needs to be accounted for as well. - Vector2 buttonSize( ( ( size.width - mPopupStyle->buttonSpacing * ( mButtons.size() + 1 ) ) / mButtons.size() ), - mPopupStyle->bottomSize.height - mPopupStyle->margin ); - - Vector3 buttonPosition( mPopupStyle->buttonSpacing, 0.0f, 0.0f ); - - for ( ActorIter iter = mButtons.begin(), endIter = mButtons.end(); - iter != endIter; - ++iter, buttonPosition.x += mPopupStyle->buttonSpacing + buttonSize.width ) - { - Actor button = *iter; - - // If there is only one button, it needs to be laid out on center. - if ( mButtons.size() == 1 ) - { - buttonPosition.x = 0.0f; - button.SetAnchorPoint( AnchorPoint::CENTER ); - button.SetParentOrigin( ParentOrigin::CENTER ); - } - else - { - button.SetAnchorPoint( AnchorPoint::CENTER_LEFT ); - button.SetParentOrigin( ParentOrigin::CENTER_LEFT ); - } - - button.SetPosition( buttonPosition ); - - button.PropagateRelayoutFlags(); // Reset relayout flags for relayout - container.Add( button, buttonSize ); - } + SetContent( child ); + } + else + { + mLayoutDirty = true; + RelayoutRequest(); } } -void Popup::OnSetResizePolicy( ResizePolicy::Type policy, Dimension::Type dimension ) +void Popup::LayoutContext( const Vector2& size ) { - if( mPopupLayout ) + // Do nothing if not in a contextual mode (or there is no parent context). + Actor self = Self(); + Actor parent = self.GetParent(); + if( ( mContextualMode == Toolkit::Popup::NON_CONTEXTUAL ) || !parent ) + { + return; + } + + mPopupContainer.SetParentOrigin( ParentOrigin::CENTER ); + // We always anchor to the CENTER, rather than a different anchor point for each contextual + // mode to allow code-reuse of the bound checking code (for maintainability). + mPopupContainer.SetAnchorPoint( AnchorPoint::CENTER ); + + // Setup with some pre-calculations for speed. + Vector3 halfStageSize( Stage().GetCurrent().GetSize() / 2.0f ); + Vector3 parentPosition( parent.GetCurrentPosition() ); + Vector2 halfSize( size / 2.0f ); + Vector2 halfParentSize( parent.GetRelayoutSize( Dimension::WIDTH ) / 2.0f, parent.GetRelayoutSize( Dimension::HEIGHT ) / 2.0f ); + Vector3 newPosition( Vector3::ZERO ); + + // Perform different positioning based on the specified contextual layout mode. + switch( mContextualMode ) { - if( policy == ResizePolicy::FIT_TO_CHILDREN ) + case Toolkit::Popup::BELOW: { - mPopupLayout.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, dimension ); - if( dimension & Dimension::HEIGHT ) - { - mPopupLayout.SetFitHeight( 1 ); - } + newPosition.x += halfSize.x - halfParentSize.x; + newPosition.y += halfSize.y + halfParentSize.y + DEFAULT_CONTEXTUAL_ADJACENCY_MARGIN.y; + break; } - else + case Toolkit::Popup::ABOVE: { - mPopupLayout.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, dimension ); - // Make the content cell fill the whole of the available space - if( dimension & Dimension::HEIGHT ) - { - mPopupLayout.SetRelativeHeight( 1, 1.0f ); - } + newPosition.x += halfSize.x - halfParentSize.x; + newPosition.y -= halfSize.y + halfParentSize.y + DEFAULT_CONTEXTUAL_ADJACENCY_MARGIN.y; + break; + } + case Toolkit::Popup::RIGHT: + { + newPosition.x += halfSize.x + halfParentSize.x + DEFAULT_CONTEXTUAL_ADJACENCY_MARGIN.x; + newPosition.y += halfSize.y - halfParentSize.y; + break; + } + case Toolkit::Popup::LEFT: + { + newPosition.x -= halfSize.x + halfParentSize.x + DEFAULT_CONTEXTUAL_ADJACENCY_MARGIN.x; + newPosition.y += halfSize.y - halfParentSize.y; + break; + } + case Toolkit::Popup::NON_CONTEXTUAL: + { + // Code won't reach here (caught earlier). + break; } } -} -bool Popup::OnKeyEvent(const KeyEvent& event) -{ - bool consumed = false; - - if(event.state == KeyEvent::Down) + // On-screen position checking. + // Check new position is not too far right. If so, correct it. + // Note: Check for right rather than left first, so if popup is too wide, the left check overrides + // the right check and we at least see the left portion of the popup (as this is more useful). + if( newPosition.x >= ( halfStageSize.x - parentPosition.x - halfSize.x - DEFAULT_CONTEXTUAL_STAGE_BORDER.x ) ) { - if (event.keyCode == Dali::DALI_KEY_ESCAPE || event.keyCode == Dali::DALI_KEY_BACK) - { - SetState(Toolkit::Popup::POPUP_HIDE); - consumed = true; - } + newPosition.x = halfStageSize.x - parentPosition.x - halfSize.x - DEFAULT_CONTEXTUAL_STAGE_BORDER.x; + } + // Check new position is not too far left. If so, correct it. + if( newPosition.x < halfSize.x - ( parentPosition.x + halfStageSize.x ) + DEFAULT_CONTEXTUAL_STAGE_BORDER.x ) + { + newPosition.x = halfSize.x - ( parentPosition.x + halfStageSize.x ) + DEFAULT_CONTEXTUAL_STAGE_BORDER.x;// - parentSize.x; + } + // Check new position is not too far down. If so, correct it. + if( newPosition.y >= ( halfStageSize.y - parentPosition.y - halfSize.y - DEFAULT_CONTEXTUAL_STAGE_BORDER.y ) ) + { + newPosition.y = halfStageSize.y - parentPosition.y - halfSize.y - DEFAULT_CONTEXTUAL_STAGE_BORDER.y; + } + // Check new position is not too far up. If so, correct it. + if( newPosition.y < halfSize.y - ( parentPosition.y + halfStageSize.y ) + DEFAULT_CONTEXTUAL_STAGE_BORDER.y ) + { + newPosition.y = halfSize.y - ( parentPosition.y + halfStageSize.y ) + DEFAULT_CONTEXTUAL_STAGE_BORDER.y; } - return consumed; + // Set the final position. + mPopupContainer.SetPosition( newPosition ); } -Vector3 Popup::GetNaturalSize() +void Popup::OnRelayout( const Vector2& size, RelayoutContainer& container ) { - float margin = 2.0f * ( POPUP_OUT_MARGIN_WIDTH + mPopupStyle->margin ); - const float maxWidth = Stage::GetCurrent().GetSize().width - margin; + Vector2 useSize( size ); - Vector3 naturalSize( 0.0f, 0.0f, 0.0f ); + // Use the Popup layouts size, unless requested to use a fixed size. + // In which case take the size set for the Popup itself. + ResizePolicy::Type widthPolicy = Self().GetResizePolicy( Dimension::WIDTH ); + ResizePolicy::Type heightPolicy = Self().GetResizePolicy( Dimension::HEIGHT ); - if ( mTitle ) + // Width calculations: + if( widthPolicy == ResizePolicy::USE_NATURAL_SIZE || widthPolicy == ResizePolicy::FIT_TO_CHILDREN ) + { + // If we using a child-based policy, take the size from the popup layout. + mPopupLayout.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::WIDTH ); + useSize.width = mPopupLayout.GetRelayoutSize( Dimension::WIDTH ); + + mPopupLayout.SetFitWidth( 0u ); + } + else { - Vector3 titleNaturalSize = mTitle.GetImplementation().GetNaturalSize(); - // Buffer to avoid errors. The width of the popup could potentially be the width of the title text. - // It was observed in this case that text wrapping was then inconsistent when seen on device - const float titleBuffer = 0.5f; - titleNaturalSize.width += titleBuffer; + // If we using a parent-based policy, take the size from the popup object itself (self). + mPopupLayout.SetResizePolicy( ResizePolicy::USE_ASSIGNED_SIZE, Dimension::WIDTH ); - // As TextLabel GetNaturalSize does not take wrapping into account, limit the width - // to that of the stage - if( titleNaturalSize.width >= maxWidth) + mPopupLayout.SetFixedWidth( 0u, useSize.width ); + } + + // Height calculations: + // Title: Let the title be as high as it needs to be. + mPopupLayout.SetFitHeight( 0u ); + + // Footer: Convert the footer's resize policy to a TableView row policy. + if( mFooter ) + { + ResizePolicy::Type footerHeightPolicy = mFooter.GetResizePolicy( Dimension::HEIGHT ); + if( ( footerHeightPolicy == ResizePolicy::USE_NATURAL_SIZE ) || + ( footerHeightPolicy == ResizePolicy::FIT_TO_CHILDREN ) ) { - naturalSize.width = maxWidth; - naturalSize.height = mTitle.GetImplementation().GetHeightForWidth( naturalSize.width ); + mPopupLayout.SetFitHeight( 2u ); + } + else if( footerHeightPolicy == ResizePolicy::FIXED ) + { + mPopupLayout.SetFixedHeight( 2u, mFooter.GetRelayoutSize( Dimension::HEIGHT) ); } else { - naturalSize += titleNaturalSize; + mPopupLayout.SetRelativeHeight( 2u, 1.0f ); } + } + else + { + mPopupLayout.SetFixedHeight( 2u, 0.0f ); + } + + // Popup contents: Adjust the tableview's policies based on the popup's policies. + if( heightPolicy == ResizePolicy::USE_NATURAL_SIZE || heightPolicy == ResizePolicy::FIT_TO_CHILDREN ) + { + mPopupLayout.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT ); + + // Let both the contents expand as necessary. + mPopupLayout.SetFitHeight( 1u ); + useSize.height = mPopupLayout.GetRelayoutSize( Dimension::HEIGHT ); + } + else + { + mPopupLayout.SetResizePolicy( heightPolicy, Dimension::HEIGHT ); - naturalSize.height += mPopupStyle->margin; + // Let the content expand to fill the remaining space. + mPopupLayout.SetRelativeHeight( 1u, 1.0f ); + mPopupLayout.SetResizePolicy( ResizePolicy::USE_ASSIGNED_SIZE, Dimension::HEIGHT ); } + // Relayout the popup-layout to give it it's new size this frame. + container.Add( mPopupLayout, useSize ); + if( mContent ) { - Vector3 contentSize = mContent.GetNaturalSize(); - // Choose the biggest width - naturalSize.width = std::max( naturalSize.width, contentSize.width ); - if( naturalSize.width > maxWidth ) - { - naturalSize.width = maxWidth; - contentSize.height = mContent.GetHeightForWidth( maxWidth ); - } - naturalSize.height += contentSize.height + mPopupStyle->margin; + container.Add( mContent, Vector2( mContent.GetRelayoutSize( Dimension::WIDTH ), mContent.GetRelayoutSize( Dimension::HEIGHT ) ) ); } - if( !mButtons.empty() ) + // Perform contextual layout setup if required. + // This is done each time in case the parent moves. + // This will have no effect if no contextual mode is selected. + LayoutContext( useSize ); +} + +void Popup::OnSetResizePolicy( ResizePolicy::Type policy, Dimension::Type dimension ) +{ + // To get the popup to emulate fit-to-children, we need to actually set use-natural-size. + if( ( dimension & Dimension::HEIGHT ) && ( policy == ResizePolicy::FIT_TO_CHILDREN ) ) { - naturalSize.height += mPopupStyle->bottomSize.height; + Self().SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT ); } - // Add the margins - naturalSize.width += margin; - naturalSize.height += margin; + mLayoutDirty = true; + return; +} - return naturalSize; +Vector3 Popup::GetNaturalSize() +{ + return mPopupLayout.GetNaturalSize(); } float Popup::GetHeightForWidth( float width ) { - float height( 0.0f ); - float popupWidth( width - 2.f * ( POPUP_OUT_MARGIN_WIDTH + mPopupStyle->margin ) ); + return mPopupLayout.GetHeightForWidth( width ); +} + +float Popup::GetWidthForHeight( float height ) +{ + return mPopupLayout.GetWidthForHeight( height ); +} - if ( mTitle ) +bool Popup::OnKeyEvent( const KeyEvent& event ) +{ + // Allow events to pass through if touch transparency is enabled. + if( mTouchTransparent ) { - height += mTitle.GetImplementation().GetHeightForWidth( popupWidth ); - height += mPopupStyle->margin; + return false; } - if( mContent ) + bool consumed = false; + + if( event.state == KeyEvent::Down ) { - height += mContent.GetHeightForWidth( popupWidth ) + mPopupStyle->margin; + if (event.keyCode == Dali::DALI_KEY_ESCAPE || event.keyCode == Dali::DALI_KEY_BACK) + { + SetDisplayState( Toolkit::Popup::HIDDEN ); + consumed = true; + } } - if( !mButtons.empty() ) + return consumed; +} + +void Popup::AddFocusableChildrenRecursive( Actor parent, std::vector< Actor >& focusableActors ) +{ + if( parent ) { - height += mPopupStyle->bottomSize.height; - } + Toolkit::Control control = Toolkit::Control::DownCast( parent ); + bool layoutControl = control && GetImplementation( control ).IsKeyboardNavigationSupported(); - // Add the margins - float margin( 2.0f * ( POPUP_OUT_MARGIN_WIDTH + mPopupStyle->margin ) ); - height += margin; + if( parent.IsKeyboardFocusable() || layoutControl ) + { + focusableActors.push_back( parent ); - return height; + if( !layoutControl ) + { + for( unsigned int i = 0, numberChildren = parent.GetChildCount(); i < numberChildren; ++i ) + { + Actor child( parent.GetChildAt( i ) ); + AddFocusableChildrenRecursive( child, focusableActors ); + } + } + } + } } -float Popup::GetWidthForHeight( float height ) +void Popup::AddFocusableChildren( Actor parent, std::vector< Actor >& focusableActors ) { - return GetNaturalSize().width; + if( parent ) + { + Toolkit::Control control = Toolkit::Control::DownCast( parent ); + if( !GetImplementation( control ).IsKeyboardNavigationSupported() ) + { + for( unsigned int i = 0, numberChildren = parent.GetChildCount(); i < numberChildren; ++i ) + { + Actor child( parent.GetChildAt( i ) ); + AddFocusableChildrenRecursive( child, focusableActors ); + } + } + else + { + focusableActors.push_back( parent ); + } + } } -Actor Popup::GetNextKeyboardFocusableActor(Actor currentFocusedActor, Toolkit::Control::KeyboardFocusNavigationDirection direction, bool loopEnabled) +Actor Popup::GetNextKeyboardFocusableActor( Actor currentFocusedActor, Toolkit::Control::KeyboardFocus::Direction direction, bool loopEnabled ) { + std::string currentStr; + if( currentFocusedActor ) + { + currentStr = currentFocusedActor.GetName(); + } + Actor nextFocusableActor( currentFocusedActor ); + Actor currentFocusGroup; + if( currentFocusedActor ) + { + currentFocusGroup = KeyboardFocusManager::Get().GetFocusGroup( currentFocusedActor ); + } // TODO: Needs to be optimised - - if ( !currentFocusedActor || ( currentFocusedActor && KeyboardFocusManager::Get().GetFocusGroup(currentFocusedActor) != Self() ) ) + // The following statement checks that if we have a current focused actor, then the current focus group is not the popup content or footer. + // This is to detect if the focus is currently outside the popup, and if so, move it inside. + if( !currentFocusedActor || + ( currentFocusedActor && ( ( !mContent || ( currentFocusGroup != mContent ) ) && ( !mFooter || ( currentFocusGroup != mFooter ) ) ) ) ) { - // The current focused actor is not within popup + // The current focused actor is not within popup. if( mContent && mContent.IsKeyboardFocusable() ) { - // If content is focusable, move the focus to content + // If the content is focusable, move the focus to the content. nextFocusableActor = mContent; } - else if( !mButtons.empty() ) + else if( mFooter && mFooter.IsKeyboardFocusable() ) { - // Otherwise, movethe focus to the first button - nextFocusableActor = mButtons[0]; + // If the footer is focusable, move the focus to the footer. + nextFocusableActor = mFooter; } } else { - // Rebuild the focus chain because button or content can be added or removed dynamically - ActorContainer focusableActors; - if( mContent && mContent.IsKeyboardFocusable() ) - { - focusableActors.push_back(mContent); - } + // Rebuild the focus chain because controls or content can be added or removed dynamically + std::vector< Actor > focusableActors; - for(unsigned int i = 0; i < mButtons.size(); i++) + AddFocusableChildren( mContent, focusableActors ); + AddFocusableChildren( mFooter, focusableActors ); + + std::vector< Actor >::iterator endIterator = focusableActors.end(); + std::vector< Actor >::iterator currentIterator = focusableActors.begin(); + for( std::vector< Actor >::iterator iterator = focusableActors.begin(); iterator != endIterator; ++iterator ) { - if( mButtons[i] && mButtons[i].IsKeyboardFocusable() ) + if( currentFocusedActor == *iterator ) { - focusableActors.push_back(mButtons[i]); + currentIterator = iterator; } } - for ( ActorContainer::iterator iter = focusableActors.begin(), end = focusableActors.end(); iter != end; ++iter ) + if( currentIterator != endIterator ) { - if ( currentFocusedActor == *iter ) + switch( direction ) { - switch ( direction ) + case Toolkit::Control::KeyboardFocus::LEFT: { - case Toolkit::Control::Left: + if( currentIterator == focusableActors.begin() ) { - if ( iter == focusableActors.begin() ) - { - nextFocusableActor = *( focusableActors.end() - 1 ); - } - else - { - nextFocusableActor = *( iter - 1 ); - } - break; + nextFocusableActor = *( endIterator - 1 ); } - case Toolkit::Control::Right: + else { - if ( iter == focusableActors.end() - 1 ) - { - nextFocusableActor = *( focusableActors.begin() ); - } - else - { - nextFocusableActor = *( iter + 1 ); - } - break; + nextFocusableActor = *( currentIterator - 1 ); } - - case Toolkit::Control::Up: + break; + } + case Toolkit::Control::KeyboardFocus::RIGHT: + { + if( currentIterator == endIterator - 1 ) { - if ( *iter == mContent ) - { - nextFocusableActor = *( focusableActors.end() - 1 ); - } - else - { - if ( mContent && mContent.IsKeyboardFocusable() ) - { - nextFocusableActor = mContent; - } - else - { - if ( iter == focusableActors.begin() ) - { - nextFocusableActor = *( focusableActors.end() - 1 ); - } - else - { - nextFocusableActor = *( iter - 1 ); - } - } - } - break; + nextFocusableActor = *( focusableActors.begin() ); } - - case Toolkit::Control::Down: + else { - if ( mContent && mContent.IsKeyboardFocusable() ) - { - nextFocusableActor = mContent; - } - else - { - if ( iter == focusableActors.end() - 1 ) - { - nextFocusableActor = *( focusableActors.begin() ); - } - else - { - nextFocusableActor = *( iter + 1 ); - } - } - - if ( *iter == mContent && !mButtons.empty() ) - { - nextFocusableActor = mButtons[0]; - } - break; + nextFocusableActor = *( currentIterator + 1 ); } + break; } - if(!nextFocusableActor) + case Toolkit::Control::KeyboardFocus::UP: { - DALI_LOG_WARNING("Can not decide next focusable actor\n"); + nextFocusableActor = *( focusableActors.begin() ); + break; } - break; + case Toolkit::Control::KeyboardFocus::DOWN: + { + nextFocusableActor = *( endIterator - 1 ); + break; + } + + default: + { + break; + } + } + + if( !nextFocusableActor ) + { + DALI_LOG_WARNING( "Can not decide next focusable actor\n" ); } } } @@ -943,6 +1965,7 @@ Actor Popup::GetNextKeyboardFocusableActor(Actor currentFocusedActor, Toolkit::C return nextFocusableActor; } + } // namespace Internal } // namespace Toolkit