X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali-toolkit%2Finternal%2Fcontrols%2Fpopup%2Fpopup-impl.cpp;h=979492137a57f8cf6d8e5fde251c2c8115c1a990;hb=9c1f4310db72879676b5aca2875fbf67b97a4b0a;hp=f7c4188f03382662d49a3a7d885503d7f3a3f621;hpb=a3353d4f3763da656966e8e9a1c223c7e9585a13;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git 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 f7c4188..cf4e02c --- 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) 2021 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. @@ -19,924 +19,1954 @@ #include // EXTERNAL INCLUDES -#include // for strcmp -#include +#include #include +#include +#include +#include +#include #include -#include #include #include -#include +#include #include -#include #include -#include +#include // for strcmp // INTERNAL INCLUDES -#include -#include -#include -#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include using namespace Dali; namespace Dali { - namespace Toolkit { - 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. + +/** + * Creation function for named type "popupToast". + * @return Handle to the new toast popup object. + */ +BaseHandle CreateToast() +{ + Toolkit::Popup popup = Toolkit::Popup::New(); + + // Setup for Toast Popup type. + popup.SetProperty(Actor::Property::SIZE_MODE_FACTOR, 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.SetProperty(Actor::Property::PARENT_ORIGIN, DEFAULT_TOAST_BOTTOM_PARENT_ORIGIN); + popup.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::BOTTOM_CENTER); + + // Let events pass through the toast popup. + popup.SetProperty(Toolkit::Popup::Property::TOUCH_TRANSPARENT, true); + + return popup; +} -DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "touched-outside", SIGNAL_TOUCHED_OUTSIDE ) -DALI_SIGNAL_REGISTRATION( Toolkit, Popup, "hidden", SIGNAL_HIDDEN ) +// clang-format off +// Setup properties, signals and actions using the type-registry. +DALI_TYPE_REGISTRATION_BEGIN(Toolkit::Popup, Toolkit::Control, Create ) + +// 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 ) DALI_TYPE_REGISTRATION_END() -// Properties -const char* const PROPERTY_TITLE = "title"; -const char* const PROPERTY_STATE = "state"; +// Named type registration. -const float POPUP_ANIMATION_DURATION = 0.45f; ///< Duration of hide/show animations +// Toast Popup: Non-modal popup that displays information at the bottom of the screen. +TypeRegistration typeRegistrationToast("PopupToast", typeid( Toolkit::Popup ), CreateToast); -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 + // 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 +Popup::Popup() +: Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)), + mTouchedOutsideSignal(), + mShowingSignal(), + mShownSignal(), + mHidingSignal(), + mHiddenSignal(), + mLayer(), + mPopupLayout(), + mBacking(), + mPreviousFocusedActor(), + mTailImage(), + mPopupContainer(), + mAnimation(), mAlterAddedChild(false), - mPopupStyle(PopupStylePtr(&style)), - mPropertyTitle(Property::INVALID_INDEX), - mPropertyState(Property::INVALID_INDEX) + 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(), + mTailDownImage(), + mTailLeftImage(), + mTailRightImage() { - SetKeyboardNavigationSupport( true ); + SetKeyboardNavigationSupport(true); + + const std::string imageDirPath = AssetManager::GetDaliImagePath(); + mTailUpImage = imageDirPath + DEFAULT_TAIL_UP_IMAGE_FILE_NAME; + mTailDownImage = imageDirPath + DEFAULT_TAIL_DOWN_IMAGE_FILE_NAME; + mTailLeftImage = imageDirPath + DEFAULT_TAIL_LEFT_IMAGE_FILE_NAME; + mTailRightImage = imageDirPath + DEFAULT_TAIL_RIGHT_IMAGE_FILE_NAME; } 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.SetProperty(Dali::Actor::Property::NAME, "popup"); + + // Apply some default resizing rules. + self.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + self.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + + self.SetProperty(Actor::Property::SIZE_MODE_FACTOR, DEFAULT_POPUP_PARENT_RELATIVE_SIZE); + self.SetResizePolicy(ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH); + self.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT); - // Create Layer + // 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.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; - - // Add Dialog ( background image, title, content container, button container and tail ) - CreateDialog(); - - mLayer.Add( self ); - - 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 ); - 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 ); - - // Any content after this point which is added to Self() will be reparented to - // mContent. - mAlterAddedChild = true; + mLayer.SetProperty(Dali::Actor::Property::NAME, "popupLayer"); + + mLayer.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + mLayer.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + mLayer.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS); + + // Important to set as invisible as otherwise, if the popup is parented, + // but not shown yet it will appear statically on the screen. + mLayer.SetProperty(Actor::Property::VISIBLE, false); - // Default content. -// ShowTail(ParentOrigin::BOTTOM_CENTER); + // Add the layer to the hierarchy. + self.Add(mLayer); - // Hide content by default. - SetState( Toolkit::Popup::POPUP_HIDE, 0.0f ); + // Add Backing (Dimmed effect). + mBacking = CreateBacking(); + mLayer.Add(mBacking); - mPropertyTitle = self.RegisterProperty( PROPERTY_TITLE, "", Property::READ_WRITE ); - mPropertyState = self.RegisterProperty( PROPERTY_STATE, "POPUP_HIDE", Property::READ_WRITE ); + mPopupContainer = Actor::New(); + mPopupContainer.SetProperty(Dali::Actor::Property::NAME, "popupContainer"); + mPopupContainer.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + mPopupContainer.SetProperty(Actor::Property::ANCHOR_POINT, 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); + + // Adds the default background image. + const std::string imageDirPath = AssetManager::GetDaliImagePath(); + SetPopupBackgroundImage(Toolkit::ImageView::New(imageDirPath + DEFAULT_BACKGROUND_IMAGE_FILE_NAME)); + + mPopupLayout.SetProperty(Dali::Actor::Property::NAME, "popupLayoutTable"); + mPopupLayout.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + mPopupLayout.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + + mPopupLayout.SetResizePolicy(ResizePolicy::USE_ASSIGNED_SIZE, Dimension::WIDTH); + mPopupLayout.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::HEIGHT); + mPopupLayout.SetProperty(Actor::Property::SIZE, Vector2(Stage::GetCurrent().GetSize().x * DEFAULT_RELATIVE_PARENT_WIDTH, 0.0f)); + + mPopupLayout.SetFitHeight(0); // Set row to fit. + mPopupLayout.SetFitHeight(1); // Set row to fit. + + mPopupContainer.Add(mPopupLayout); + + // 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); + + SetupTouch(); + + DevelControl::AppendAccessibilityAttribute(Toolkit::Control::DownCast(self), "sub-role", "Alert"); + + DevelControl::SetAccessibilityConstructor(self, [](Dali::Actor actor) { + return std::make_unique(actor, Dali::Accessibility::Role::DIALOG, 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.SetProperty(Actor::Property::SCALE, Vector3::ZERO); + break; } - else if( value == "POPUP_HIDE") + + case Toolkit::Popup::FADE: { - SetState( Toolkit::Popup::POPUP_HIDE, 0.0f ); + // Fade animations start transparent. + mPopupContainer.SetProperty(Actor::Property::OPACITY, 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.SetProperty(Actor::Property::SCALE, 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.SetProperty(Actor::Property::OPACITY, 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) { - mPopupLayout.RemoveChildAt( Toolkit::TableView::CellPosition( 0, 0 ) ); + mAnimation.SetCurrentProgress(1.0f); + 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 if(duration > Math::MACHINE_EPSILON_0) + { + // Run the animation. + mAnimation.FinishedSignal().Connect(this, &Popup::OnDisplayChangeAnimationFinished); + mAnimation.Play(); + } + 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; + + mLayer.SetProperty(Actor::Property::VISIBLE, false); + mPopupLayout.SetProperty(Actor::Property::SENSITIVE, false); + + // Guard against destruction during signal emission. + Toolkit::Popup handle(GetOwner()); + mHiddenSignal.Emit(); } + else if(mDisplayState == Toolkit::Popup::SHOWING) + { + mDisplayState = Toolkit::Popup::SHOWN; + Toolkit::Popup handle(GetOwner()); + mShownSignal.Emit(); - return std::string(); + // 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::CreateFooter() +bool Popup::OnAutoHideTimeReached() { - if( !mBottomBg ) + if(!Dali::Accessibility::IsUp() || true) // TODO: remove 'true' in sync with EFL (UX change) { - // Adds bottom background - mBottomBg = Actor::New(); - mBottomBg.SetName( "POPUP_BOTTOM_BG" ); - mBottomBg.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); + // Display timer has expired, auto hide the popup exactly as if the user had clicked outside. + SetDisplayState(Toolkit::Popup::HIDDEN); + } - mPopupLayout.SetFixedHeight( 2, mPopupStyle->bottomSize.height ); // Buttons - mPopupLayout.AddChild( mBottomBg, Toolkit::TableView::CellPosition( 2, 0 ) ); + if(mAutoHideTimer) + { + mAutoHideTimer.Stop(); + mAutoHideTimer.TickSignal().Disconnect(this, &Popup::OnAutoHideTimeReached); + mAutoHideTimer.Reset(); } + return true; } -void Popup::AddButton( Toolkit::Button button ) +void Popup::SetPopupBackgroundImage(Actor image) { - mButtons.push_back( button ); - button.SetResizePolicy( ResizePolicy::USE_ASSIGNED_SIZE, Dimension::ALL_DIMENSIONS ); // Size will be assigned to it - - // If this is the first button added - if( mButtons.size() == 1 ) + // Removes any previous background. + if(mPopupBackgroundImage) { - CreateFooter(); - - if( mButtonAreaImage ) + mPopupBackgroundImage.Unparent(); + if(mTailImage) { - mBottomBg.Add( mButtonAreaImage ); + mTailImage.Unparent(); } } - mBottomBg.Add( button ); + // Adds new background to the dialog. + mPopupBackgroundImage = image; + mPopupBackgroundImage.SetProperty(Dali::Actor::Property::NAME, "popupBackgroundImage"); + mPopupBackgroundImage.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + mPopupBackgroundImage.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); - RelayoutRequest(); -} + // Set the popup border to be slightly larger than the layout contents. + UpdateBackgroundPositionAndSize(); -void Popup::SetState( Toolkit::Popup::PopupState state ) -{ - SetState( state, POPUP_ANIMATION_DURATION ); -} + const bool prevAlter = mAlterAddedChild; + mAlterAddedChild = false; + mPopupContainer.Add(mPopupBackgroundImage); + mPopupBackgroundImage.LowerToBottom(); + mAlterAddedChild = prevAlter; -void Popup::SetState( Toolkit::Popup::PopupState state, float duration ) -{ - // default animation behaviour. - HandleStateChange(state, duration); + 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) + if(mTitle) { - image = mPopupStyle->tailLeftImage; - } - else if(position.x > 1.0f - Math::MACHINE_EPSILON_1) - { - 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); - - CreateFooter(); + // Set up padding to give sensible default behaviour + // (an application developer can later override this if they wish). + mTitle.SetProperty(Actor::Property::PADDING, DEFAULT_TITLE_PADDING); - 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.SetProperty(Dali::Actor::Property::NAME, "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.WheelEventSignal().Connect(this, &Popup::OnBackingWheelEvent); + 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; + auto* accessible = Dali::Accessibility::Accessible::Get(Self()); + + 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; + Dali::Accessibility::Bridge::GetCurrentBridge()->RegisterDefaultLabel(accessible); + accessible->EmitShowing(true); - // 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.SetProperty(Actor::Property::VISIBLE, 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.SetProperty(Actor::Property::SENSITIVE, 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().GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - mPreviousFocusedActor = keyboardFocusManager.GetCurrentFocusActor(); - - if( mContent && mContent.IsKeyboardFocusable() ) + // Setup the actgor to start focus from. + Actor focusActor; + if(mContent && mContent.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - // 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.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - // 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"); } + + if(focusActor) + { + keyboardFocusManager.SetCurrentFocusActor(focusActor); + } } - break; } } - - Actor self = Self(); - if(duration > Math::MACHINE_EPSILON_1) + else // Not visible. { - if ( mAnimation ) + mDisplayState = Toolkit::Popup::HIDING; + Dali::Accessibility::Bridge::GetCurrentBridge()->UnregisterDefaultLabel(accessible); + ClearKeyInputFocus(); + accessible->EmitShowing(false); + // Restore the keyboard focus when popup is hidden. + if(mPreviousFocusedActor && mPreviousFocusedActor.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - 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); +} - if(mShowing) +Toolkit::Popup::DisplayState Popup::GetDisplayState() const +{ + return mDisplayState; +} + +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.SetProperty(Actor::Property::PARENT_ORIGIN, Self().GetCurrentProperty(Actor::Property::PARENT_ORIGIN)); + mPopupContainer.SetProperty(Actor::Property::ANCHOR_POINT, Self().GetCurrentProperty(Actor::Property::ANCHOR_POINT)); + + // If there is only a title, use less padding. + if(mTitle) + { + if(!mContent && !mFooter) { - mAnimation.AnimateTo( Property(mBacking, Actor::Property::COLOR_ALPHA), targetBackingAlpha, AlphaFunction::EASE_IN_OUT, TimePeriod(0.0f, duration * 0.5f) ); - mAnimation.AnimateTo( Property(self, Actor::Property::SCALE), targetSize, AlphaFunction::EASE_IN_OUT, TimePeriod(duration * 0.5f, duration * 0.5f) ); + mTitle.SetProperty(Actor::Property::PADDING, DEFAULT_TITLE_ONLY_PADDING); } else { - mAnimation.AnimateTo( Property(mBacking, Actor::Property::COLOR_ALPHA), targetBackingAlpha, AlphaFunction::EASE_IN_OUT, TimePeriod(0.0f, duration * 0.5f) ); - mAnimation.AnimateTo( Property(self, Actor::Property::SCALE), targetSize, AlphaFunction::EASE_IN_OUT, TimePeriod(0.0f, duration * 0.5f) ); + mTitle.SetProperty(Actor::Property::PADDING, 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(); -void Popup::HandleStateChangeComplete() -{ - // Remove contents from stage if completely hidden. - if( ( mState == Toolkit::Popup::POPUP_HIDE ) && mLayer.GetParent() ) - { - mLayer.Unparent(); - Self().SetSensitive( false ); + // Update background visibility. + mPopupContainer.SetProperty(Actor::Property::VISIBLE, !(!mFooter && mPopupLayout.GetChildCount() == 0)); - // Guard against destruction during signal emission - Toolkit::Popup handle( GetOwner() ); - mHiddenSignal.Emit(); - } -} + // Create / destroy / position the tail as needed. + LayoutTail(); -Toolkit::Popup::TouchedOutsideSignalType& Popup::OutsideTouchedSignal() -{ - return mTouchedOutsideSignal; -} + // Setup any layout and initialisation required for the selected animation. + LayoutAnimation(); -Toolkit::Popup::HiddenSignalType& Popup::HiddenSignal() -{ - return mHiddenSignal; + RelayoutRequest(); } -bool Popup::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor ) +void Popup::LayoutTail() { - Dali::BaseHandle handle( object ); - - bool connected( true ); - Toolkit::Popup popup = Toolkit::Popup::DownCast( handle ); + // Removes the tail actor. + if(mTailImage && mTailImage.GetParent()) + { + mTailImage.GetParent().Remove(mTailImage); + mTailImage.Reset(); + } + + if(!mTailVisible) + { + return; + } - if( 0 == strcmp( signalName.c_str(), SIGNAL_TOUCHED_OUTSIDE ) ) + 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) { - popup.OutsideTouchedSignal().Connect( tracker, functor ); + image = mTailUpImage; + anchorPoint = AnchorPoint::BOTTOM_CENTER; + position.y = mBackgroundBorder.top; } - else if( 0 == strcmp( signalName.c_str(), SIGNAL_HIDDEN ) ) + else if(parentOrigin.y > (1.0f - Math::MACHINE_EPSILON_1)) { - popup.HiddenSignal().Connect( tracker, functor ); + image = mTailDownImage; + anchorPoint = AnchorPoint::TOP_CENTER; + position.y = -mBackgroundBorder.bottom; } - else + else if(parentOrigin.x < Math::MACHINE_EPSILON_1) { - // signalName does not match any signal - connected = false; + 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; } - return connected; + if(!image.empty()) + { + // Adds the tail actor. + mTailImage = Toolkit::ImageView::New(image); + mTailImage.SetProperty(Dali::Actor::Property::NAME, "tailImage"); + mTailImage.SetProperty(Actor::Property::PARENT_ORIGIN, parentOrigin); + mTailImage.SetProperty(Actor::Property::ANCHOR_POINT, anchorPoint); + mTailImage.SetProperty(Actor::Property::POSITION, position); + + if(mPopupBackgroundImage) + { + mPopupBackgroundImage.Add(mTailImage); + } + } } -void Popup::OnStateAnimationFinished( Animation& source ) +void Popup::SetContextualMode(Toolkit::Popup::ContextualMode mode) { - HandleStateChangeComplete(); + mContextualMode = mode; + mLayoutDirty = true; } -bool Popup::OnBackingTouched(Actor actor, const TouchEvent& event) +Toolkit::Popup::ContextualMode Popup::GetContextualMode() const { - if(event.GetPointCount()>0) - { - const TouchPoint& point = event.GetPoint(0); + return mContextualMode; +} - if(point.state == TouchPoint::Down) - { - // Guard against destruction during signal emission - Toolkit::Popup handle( GetOwner() ); +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.SetProperty(Dali::Actor::Property::NAME, "popupBacking"); - mTouchedOutsideSignal.Emit(); - } - } + // Must always be positioned top-left of stage, regardless of parent. + backing.SetProperty(Actor::Property::INHERIT_POSITION, false); - return true; + // Always the full size of the stage. + backing.SetResizePolicy(ResizePolicy::FIXED, Dimension::ALL_DIMENSIONS); + backing.SetProperty(Actor::Property::SIZE, Stage::GetCurrent().GetSize()); + + // Catch events. + backing.SetProperty(Actor::Property::SENSITIVE, true); + + // Default to being transparent. + backing.SetProperty(Actor::Property::COLOR_ALPHA, 0.0f); + backing.WheelEventSignal().Connect(this, &Popup::OnBackingWheelEvent); + return backing; } -bool Popup::OnBackingWheelEvent(Actor actor, const WheelEvent& event) +Toolkit::Popup::TouchedOutsideSignalType& Popup::OutsideTouchedSignal() { - // consume wheel event in dimmed backing actor - return true; + return mTouchedOutsideSignal; } -bool Popup::OnDialogTouched(Actor actor, const TouchEvent& event) +Toolkit::Popup::DisplayStateChangeSignalType& Popup::ShowingSignal() { - // consume event (stops backing actor receiving touch events) - return true; + return mShowingSignal; } -void Popup::OnControlChildAdd( Actor& child ) +Toolkit::Popup::DisplayStateChangeSignalType& Popup::ShownSignal() { - // 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 ) ); - } + return mShownSignal; +} + +Toolkit::Popup::DisplayStateChangeSignalType& Popup::HidingSignal() +{ + return mHidingSignal; +} + +Toolkit::Popup::DisplayStateChangeSignalType& Popup::HiddenSignal() +{ + return mHiddenSignal; +} - // keep a handle to the new content. - mContent = child; +void Popup::SetTailVisibility(bool visible) +{ + mTailVisible = visible; + mLayoutDirty = true; +} - mPopupLayout.AddChild( mContent, Toolkit::TableView::CellPosition( 1, 0 ) ); +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.SetProperty(Actor::Property::SIZE_MODE_FACTOR, 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.SetProperty(Actor::Property::POSITION, Vector2((mBackgroundBorder.right - mBackgroundBorder.left) * 0.5f, (mBackgroundBorder.bottom - mBackgroundBorder.top) * 0.5f)); } } -void Popup::OnRelayout( const Vector2& size, RelayoutContainer& container ) +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) { - // Hide the background image - mBackgroundImage.SetVisible( !( mButtons.empty() && mPopupLayout.GetChildCount() == 0 ) ); + mTailLeftImage = image; + mLayoutDirty = true; + LayoutTail(); +} - // Relayout All buttons - if( !mButtons.empty() ) +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) +{ + if(mTouchTransparent != enabled) { - // 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 ); + mTouchTransparent = enabled; + SetupTouch(); + } +} - Vector3 buttonPosition( mPopupStyle->buttonSpacing, 0.0f, 0.0f ); +const bool Popup::IsTouchTransparent() const +{ + return mTouchTransparent; +} - for( std::vector< Actor >::iterator iter = mButtons.begin(), endIter = mButtons.end(); - iter != endIter; - ++iter, buttonPosition.x += mPopupStyle->buttonSpacing + buttonSize.width ) - { - Actor button = *iter; +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)); - // If there is only one button, it needs to be laid out on center. - if ( mButtons.size() == 1 ) + switch(propertyIndex) + { + case Toolkit::Popup::Property::TITLE: { - buttonPosition.x = 0.0f; - button.SetAnchorPoint( AnchorPoint::CENTER ); - button.SetParentOrigin( ParentOrigin::CENTER ); + Property::Map valueMap; + if(value.Get(valueMap)) + { + popupImpl.SetTitle(Scripting::NewActor(valueMap)); + } + break; } - else + 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: { - button.SetAnchorPoint( AnchorPoint::CENTER_LEFT ); - button.SetParentOrigin( ParentOrigin::CENTER_LEFT ); + std::string valueString; + if(value.Get(valueString)) + { + Toolkit::Popup::DisplayState displayState(Toolkit::Popup::HIDDEN); + if(Scripting::GetEnumeration(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(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(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; - button.SetPosition( buttonPosition ); + 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; + } - //Todo: Use the size negotiation pass instead of SetSize directly - button.SetSize( buttonSize ); + 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; + } } } } -void Popup::OnSetResizePolicy( ResizePolicy::Type policy, Dimension::Type dimension ) +Property::Value Popup::GetProperty(BaseObject* object, Property::Index propertyIndex) { - if( mPopupLayout ) + Property::Value value; + + Toolkit::Popup popup = Toolkit::Popup::DownCast(Dali::BaseHandle(object)); + + if(popup) { - if( policy == ResizePolicy::FIT_TO_CHILDREN ) + Popup& popupImpl(GetImpl(popup)); + + switch(propertyIndex) { - mPopupLayout.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, dimension ); - if( dimension & Dimension::HEIGHT ) + case Toolkit::Popup::Property::TITLE: { - mPopupLayout.SetFitHeight( 1 ); + Property::Map map; + Scripting::CreatePropertyMap(popupImpl.GetTitle(), map); + value = map; + break; } - } - else - { - mPopupLayout.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, dimension ); - // Make the content cell fill the whole of the available space - if( dimension & Dimension::HEIGHT ) + case Toolkit::Popup::Property::CONTENT: { - mPopupLayout.SetRelativeHeight( 1, 1.0f ); + 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(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(popupImpl.GetContextualMode(), ContextualModeTable, ContextualModeTableCount); + break; + } + case Toolkit::Popup::Property::ANIMATION_DURATION: + { + value = popupImpl.GetAnimationDuration(); + break; + } + case Toolkit::Popup::Property::ANIMATION_MODE: + { + value = Scripting::GetLinearEnumerationName(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::OnKeyEvent(const KeyEvent& event) +bool Popup::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor) { - bool consumed = false; + Dali::BaseHandle handle(object); + + bool connected(true); + Toolkit::Popup popup = Toolkit::Popup::DownCast(handle); - if(event.state == KeyEvent::Down) + if(0 == strcmp(signalName.c_str(), SIGNAL_TOUCHED_OUTSIDE)) { - if (event.keyCode == Dali::DALI_KEY_ESCAPE || event.keyCode == Dali::DALI_KEY_BACK) + 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); + } + else + { + // signalName does not match any signal + connected = false; + } + + return connected; +} + +bool Popup::OnBackingTouched(Actor actor, const TouchEvent& touch) +{ + // Allow events to pass through if the backing isn't the hit-actor + if((touch.GetHitActor(0) == actor) && + (touch.GetPointCount() > 0) && + (touch.GetState(0) == PointState::DOWN)) + { + // Guard against destruction during signal emission. + Toolkit::Popup handle(GetOwner()); + + mTouchedOutsideSignal.Emit(); + } + + return false; +} + +bool Popup::OnBackingWheelEvent(Actor actor, const WheelEvent& event) +{ + // Allow events to pass through if touch transparency is enabled. + if(mTouchTransparent) + { + return false; + } + + return true; +} + +bool Popup::OnDialogTouched(Actor actor, const TouchEvent& touch) +{ + // Only connecting this so the backing does not become the default hit-actor and inadvertently closes the popup + return false; +} + +void Popup::OnSceneConnection(int depth) +{ + mLayoutDirty = true; + RelayoutRequest(); + + Control::OnSceneConnection(depth); +} + +void Popup::OnChildAdd(Actor& child) +{ + // Re-parent any children added by user to the body layer. + if(mAlterAddedChild) + { + SetContent(child); + } + else + { + mLayoutDirty = true; + RelayoutRequest(); + } + + Control::OnChildAdd(child); +} + +void Popup::LayoutContext(const Vector2& size) +{ + // 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.SetProperty(Actor::Property::PARENT_ORIGIN, 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.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + + // Setup with some pre-calculations for speed. + Vector3 halfStageSize(Stage().GetCurrent().GetSize() / 2.0f); + Vector3 parentPosition(parent.GetCurrentProperty(Actor::Property::POSITION)); + 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) + { + case Toolkit::Popup::BELOW: { - SetState(Toolkit::Popup::POPUP_HIDE); - consumed = true; + newPosition.x += halfSize.x - halfParentSize.x; + newPosition.y += halfSize.y + halfParentSize.y + DEFAULT_CONTEXTUAL_ADJACENCY_MARGIN.y; + break; + } + case Toolkit::Popup::ABOVE: + { + 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; } } - return consumed; + // 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)) + { + 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; + } + + // Set the final position. + mPopupContainer.SetProperty(Actor::Property::POSITION, 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); + + // 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); - if ( mTitle ) + 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); + + mPopupLayout.SetFixedWidth(0u, useSize.width); + } - // As TextLabel GetNaturalSize does not take wrapping into account, limit the width - // to that of the stage - if( titleNaturalSize.width >= maxWidth) + // 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)) + { + mPopupLayout.SetFitHeight(2u); + } + else if(footerHeightPolicy == ResizePolicy::FIXED) { - naturalSize.width = maxWidth; - naturalSize.height = mTitle.GetImplementation().GetHeightForWidth( naturalSize.width ); + 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); } - if( mContent ) + // 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 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.GetState() == KeyEvent::DOWN) { - height += mContent.GetHeightForWidth( popupWidth ) + mPopupStyle->margin; + if(event.GetKeyCode() == Dali::DALI_KEY_ESCAPE || event.GetKeyCode() == Dali::DALI_KEY_BACK) + { + SetDisplayState(Toolkit::Popup::HIDDEN); + consumed = true; + } } - if( !mButtons.empty() ) + return consumed; +} + +void Popup::AddFocusableChildrenRecursive(Actor parent, std::vector& 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.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE) || 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& 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::KeyboardFocus::Direction direction, bool loopEnabled) { - Actor nextFocusableActor( currentFocusedActor ); + std::string currentStr; + if(currentFocusedActor) + { + currentStr = currentFocusedActor.GetProperty(Dali::Actor::Property::NAME); + } - // TODO: Needs to be optimised + Actor nextFocusableActor(currentFocusedActor); + Actor currentFocusGroup; + if(currentFocusedActor) + { + currentFocusGroup = KeyboardFocusManager::Get().GetFocusGroup(currentFocusedActor); + } - if ( !currentFocusedActor || ( currentFocusedActor && KeyboardFocusManager::Get().GetFocusGroup(currentFocusedActor) != Self() ) ) + // TODO: Needs to be optimised + // 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 - if( mContent && mContent.IsKeyboardFocusable() ) + // The current focused actor is not within popup. + if(mContent && mContent.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - // 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.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) { - // 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 - std::vector< Actor > 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 focusableActors; + + AddFocusableChildren(mContent, focusableActors); + AddFocusableChildren(mFooter, focusableActors); - for(unsigned int i = 0; i < mButtons.size(); i++) + std::vector::iterator endIterator = focusableActors.end(); + std::vector::iterator currentIterator = focusableActors.begin(); + for(std::vector::iterator iterator = focusableActors.begin(); iterator != endIterator; ++iterator) { - if( mButtons[i] && mButtons[i].IsKeyboardFocusable() ) + if(currentFocusedActor == *iterator) { - focusableActors.push_back(mButtons[i]); + currentIterator = iterator; } } - for( std::vector< Actor >::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::KeyboardFocus::LEFT: + if(currentIterator == focusableActors.begin()) { - if ( iter == focusableActors.begin() ) - { - nextFocusableActor = *( focusableActors.end() - 1 ); - } - else - { - nextFocusableActor = *( iter - 1 ); - } - break; + nextFocusableActor = *(endIterator - 1); } - case Toolkit::Control::KeyboardFocus::RIGHT: + else { - if ( iter == focusableActors.end() - 1 ) - { - nextFocusableActor = *( focusableActors.begin() ); - } - else - { - nextFocusableActor = *( iter + 1 ); - } - break; + nextFocusableActor = *(currentIterator - 1); } - - case Toolkit::Control::KeyboardFocus::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::KeyboardFocus::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"); } } } @@ -944,6 +1974,59 @@ Actor Popup::GetNextKeyboardFocusableActor(Actor currentFocusedActor, Toolkit::C return nextFocusableActor; } +void Popup::SetupTouch() +{ + if(!mTouchTransparent) + { + // Connect all the signals and set us up to consume all touch events + mBacking.TouchedSignal().Connect(this, &Popup::OnBackingTouched); + mPopupBackgroundImage.TouchedSignal().Connect(this, &Popup::OnDialogTouched); + mPopupLayout.TouchedSignal().Connect(this, &Popup::OnDialogTouched); + mLayer.SetProperty(Layer::Property::CONSUMES_TOUCH, true); + } + else + { + // We are touch transparent so disconnect all signals and ensure our layer does not consumed all touch events + mBacking.TouchedSignal().Disconnect(this, &Popup::OnBackingTouched); + mPopupBackgroundImage.TouchedSignal().Disconnect(this, &Popup::OnDialogTouched); + mPopupLayout.TouchedSignal().Disconnect(this, &Popup::OnDialogTouched); + mLayer.SetProperty(Layer::Property::CONSUMES_TOUCH, false); + } +} + +std::string Popup::PopupAccessible::GetNameRaw() const +{ + auto popup = Toolkit::Popup::DownCast(Self()); + std::string title; + Actor popupTitle = popup.GetTitle(); + if(popupTitle) + { + std::string titleText = popupTitle.GetProperty(Toolkit::TextLabel::Property::TEXT); + title = titleText; + } + else + { + Actor popupContent = popup.GetContent(); + if(popupContent) + { + std::string contentText = popupContent.GetProperty(Toolkit::TextLabel::Property::TEXT); + title = contentText; + } + } + return title; +} + +Dali::Accessibility::States Popup::PopupAccessible::CalculateStates() +{ + auto states = DevelControl::ControlAccessible::CalculateStates(); + auto popup = Toolkit::Popup::DownCast(Self()); + auto displayState = popup.GetProperty(Toolkit::Popup::Property::DISPLAY_STATE); + + states[Dali::Accessibility::State::SHOWING] = (displayState == "SHOWN" || displayState == "SHOWING"); + + return states; +} + } // namespace Internal } // namespace Toolkit