2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.0 (the License);
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://floralicense.org/license/
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an AS IS BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
17 #include <dali-toolkit/internal/controls/text-input/text-input-popup-impl.h>
18 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
26 const char* DEFAULT_PANEL_BACKGROUND = DALI_IMAGE_DIR "cutCopyPastePopup_bg.png";
29 const char* DEFAULT_PANEL_BUTTON_DIVIDER = DALI_IMAGE_DIR "copypanelLine.png";
31 /* Functionality in place to have the end buttons using different images to inner button.
32 * Supply a centre image and then a left and right image, the centre image can have straight ends while
33 * the left image can be rounded on the left and straight on the right, the right image can be straight on the left and rounded on the right.
36 // Popup: Left Pressed Highlight
37 const char* DEFAULT_BUTTON_HIGHLIGHT_LEFT( DALI_IMAGE_DIR "00_popup_button_pressed.png" );
38 const Vector4 DEFAULT_BUTTON_HIGHLIGHT_LEFT_BORDER( 6.0f, 9.0f, 6.0f, 9.0f );
40 // Popup: Center Pressed Highlight
41 const char* DEFAULT_BUTTON_HIGHLIGHT_CENTER( DALI_IMAGE_DIR "00_popup_button_pressed.png" );
42 const Vector4 DEFAULT_BUTTON_HIGHLIGHT_CENTER_BORDER( 6.0f, 9.0f, 6.0f, 9.0f );
44 // Popup: Right Pressed Highlight
45 const char* DEFAULT_BUTTON_HIGHLIGHT_RIGHT( DALI_IMAGE_DIR "00_popup_button_pressed.png" );
46 const Vector4 DEFAULT_BUTTON_HIGHLIGHT_RIGHT_BORDER( 6.0f, 9.0f, 6.0f, 9.0f );
49 const char* DEFAULT_POPUP_TAIL_BOTTOM( DALI_IMAGE_DIR "00_popup_bubble_tail_bottom.png" );
51 // Popup: Vertical Constraint
52 // TODO: Remove - this should come from application - it is not possible to get the
53 // height of the indicator actor from Dali-Toolkit.
54 const Vector2 DEFAULT_POPUP_INDICATOR_OFFSET(0.0f, 60.0f);
56 const Vector4 BACKGROUND_IMAGE_BORDER( 22.0f, 20.0f, 29.0f, 27.0f );
57 const Vector2 BACKGROUND_IMAGE_SIZE( 50.0f, 54.0f );
58 const Vector3 POPUP_TEXT_OFFSET( 12.0f, 10.0f, 0.0f );
59 const Vector3 POPUP_TEXT_ENLARGE( 12.0f, 28.0f, 0.0f );
60 const Vector3 POPUP_MINIMUM_SIZE( 128.0f, 124.0f, 0.0f );
62 const Vector3 BUTTON_TEXT_ENLARGE( 32.0f, 0.0f, 0.0f );
63 const Vector3 BUTTON_TEXT_MINIMUM_SIZE( 128.0f, 126.0f, 0.0f );
64 const Vector3 BUTTON_TEXT_MAXIMUM_SIZE( 196.0f, 126.0f, 0.0f );
65 const Vector3 TEXT_LABEL_MAX_SIZE( 160.0f, 30.0f, 0.0f );
67 const float DIVIDER_WIDTH(2.0f); ///< Width of each button divider
68 const float DIVIDER_MARGIN(10.0f); ///< Top/Bottom Margin between divider and edge of popup.
70 const float DEFAULT_UI_FONT_SIZE(7.0f); ///< Standard font size for Text-Input's UI
72 const float HIDE_POPUP_ANIMATION_DURATION(0.2f); ///< Duration of popup hide animation in seconds.
73 const float SHOW_POPUP_ANIMATION_DURATION(0.2f); ///< Duration of popup show animation in seconds.
75 const Vector2 DEFAULT_ICON_SIZE( 45.0f, 45.0f ); ///< Default icon size for image in options
76 const float TEXT_POSITION_OFFSET( -19.0f ); ///< Default offset for text label
77 const float ICON_POSITION_OFFSET( 19.0f ); ///< Default offset for icon
79 // TODO: This should be based on the content for example:
80 // 1. For selection: should be above top of highlighted selection, or below bottom of highlighted selection + end handle.
81 // 2. For cursor: should be above top of cursor, or below bottom of cursor + grab handle.
82 const std::string POPUP_ALTERNATIVE_OFFSET("popup-alternative-offset"); ///< Alternative offset property for confinenment constraint.
86 * Confine Actor to boundaries of reference actor (e.g. Parent)
87 * Actor bounds (top-left position + size) are confined to reference Actor's
90 struct ConfinementConstraint
93 * Confinement constraint constructor.
94 * @param[in] topLeftMargin (optional) Top-Left margins (defaults to 0.0f, 0.0f)
95 * @param[in] bottomRightMargin (optional) Bottom-Right margins (defaults to 0.0f, 0.0f)
96 * @param[in] flipVertical (optional) whether to flip Actor to the other side if near edge, and by
97 * how much (defaults to 0.0f i.e. no flip)
99 ConfinementConstraint(Vector2 topLeftMargin = Vector2::ZERO, Vector2 bottomRightMargin = Vector2::ZERO, bool flipHorizontal = false, bool flipVertical = false)
100 : mMinIndent(topLeftMargin),
101 mMaxIndent(bottomRightMargin),
102 mFlipHorizontal(flipHorizontal),
103 mFlipVertical(flipVertical)
107 Vector3 operator()(const Vector3& constPosition,
108 const PropertyInput& sizeProperty,
109 const PropertyInput& parentOriginProperty,
110 const PropertyInput& anchorPointProperty,
111 const PropertyInput& referenceSizeProperty,
112 const PropertyInput& alternativeOffsetProperty)
114 const Vector3& size = sizeProperty.GetVector3();
115 const Vector3& origin = parentOriginProperty.GetVector3();
116 const Vector3& anchor = anchorPointProperty.GetVector3();
117 const Vector3& referenceSize = referenceSizeProperty.GetVector3();
118 const Vector2& alternativeOffset = alternativeOffsetProperty.GetVector2();
120 Vector3 newPosition(constPosition);
122 // Get actual position of Actor relative to parent's Top-Left.
123 Vector3 position(constPosition + origin * referenceSize);
125 // if top-left corner is outside of Top-Left bounds, then push back in screen.
126 Vector3 corner(position - size * anchor - mMinIndent);
128 if(mFlipHorizontal && corner.x < 0.0f)
131 newPosition.x += size.width + alternativeOffset.width;
134 if(mFlipVertical && corner.y < 0.0f)
137 newPosition.y += size.height + alternativeOffset.height;
140 newPosition.x -= std::min(corner.x, 0.0f);
141 newPosition.y -= std::min(corner.y, 0.0f);
143 // if bottom-right corner is outside of Bottom-Right bounds, then push back in screen.
144 corner += size - referenceSize + mMinIndent + mMaxIndent;
146 if(mFlipHorizontal && corner.x > 0.0f)
149 newPosition.x -= size.width + alternativeOffset.width;
152 if(mFlipVertical && corner.y > 0.0f)
155 newPosition.y -= size.height + alternativeOffset.height;
158 newPosition.x -= std::max(corner.x, 0.0f);
159 newPosition.y -= std::max(corner.y, 0.0f);
164 Vector3 mMinIndent; ///< Top-Left Margin
165 Vector3 mMaxIndent; ///< Bottom-Right Margin.
166 bool mFlipHorizontal; ///< Whether to flip actor's position if exceeds horizontal screen bounds
167 bool mFlipVertical; ///< Whether to flip actor's position if exceeds vertical screen bounds
170 } // unnamed namespace
181 const char* const TextInputPopup::SIGNAL_PRESSED = "pressed";
182 const char* const TextInputPopup::SIGNAL_HIDE_FINISHED = "hide-finished";
183 const char* const TextInputPopup::SIGNAL_SHOW_FINISHED = "show-finished";
185 TextInputPopup::TextInputPopup()
186 : mState(StateHidden),
187 mRootActor(Layer::New()),
189 mHideFinishedSignal(),
190 mShowFinishedSignal()
192 mAlternativeOffsetProperty = mRootActor.RegisterProperty( POPUP_ALTERNATIVE_OFFSET, Vector2::ZERO );
193 mRootActor.SetParentOrigin( ParentOrigin::CENTER );
194 mRootActor.SetAnchorPoint( AnchorPoint::BOTTOM_CENTER );
195 // constrain popup to size of parent.
198 Actor TextInputPopup::Self()
203 void TextInputPopup::AddToStage()
205 // TODO: Confinement constraint borders should be defined by the application.
206 // It should also not use the stage directly, instead it should add to parent container.
207 Stage::GetCurrent().Add(mRootActor);
209 ApplyConfinementConstraint();
212 void TextInputPopup::ApplyConfinementConstraint()
214 mRootActor.RemoveConstraints();
215 Constraint constraint = Constraint::New<Vector3>( Actor::POSITION,
216 LocalSource( Actor::SIZE ),
217 LocalSource( Actor::PARENT_ORIGIN ),
218 LocalSource( Actor::ANCHOR_POINT ),
219 ParentSource( Actor::SIZE ),
220 LocalSource( mAlternativeOffsetProperty ),
221 ConfinementConstraint( DEFAULT_POPUP_INDICATOR_OFFSET,
225 mRootActor.ApplyConstraint(constraint);
228 void TextInputPopup::RemoveFromStage()
230 Actor rootActor = Self();
231 Stage::GetCurrent().Remove( rootActor );
234 void TextInputPopup::Clear()
238 mRootActor.Remove( mBackground );
240 mButtonContainer.clear();
241 mDividerContainer.clear();
244 mRootActor.RemoveConstraints();
246 mState = StateHidden;
250 Toolkit::TextView TextInputPopup::CreateOptionText( const MarkupProcessor::StyledTextArray& styledCaption )
252 Toolkit::TextView label = Toolkit::TextView::New( styledCaption );
253 label.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
254 label.SetWidthExceedPolicy( Toolkit::TextView::Fade );
255 label.SetParentOrigin( ParentOrigin::BOTTOM_CENTER );
256 label.SetAnchorPoint( AnchorPoint::BOTTOM_CENTER );
257 label.SetPosition( 0.0f, TEXT_POSITION_OFFSET );
262 ImageActor TextInputPopup::CreateOptionIcon( Image iconImage )
264 ImageActor icon = ImageActor::New( iconImage );
266 icon.SetSize( DEFAULT_ICON_SIZE );
267 icon.SetParentOrigin( ParentOrigin::TOP_CENTER );
268 icon.SetAnchorPoint( AnchorPoint::TOP_CENTER );
269 icon.SetPosition( 0.0f, ICON_POSITION_OFFSET );
274 void TextInputPopup::CreatePopUpBackground()
276 // Create background-panel if not already created (required if we have at least one option)
279 Image backgroundImage = Image::New( DEFAULT_PANEL_BACKGROUND );
281 mBackground = ImageActor::New( backgroundImage );
282 // Expand background from bottom-center of root actor.
283 mBackground.SetParentOrigin( ParentOrigin::BOTTOM_CENTER );
284 mBackground.SetAnchorPoint( AnchorPoint::BOTTOM_CENTER );
285 mBackground.SetStyle( ImageActor::STYLE_NINE_PATCH );
287 mBackground.SetNinePatchBorder( Vector4(13.0f, 13.0f, 13.0f, 13.0f) );
289 Self().Add( mBackground );
290 mContentSize = POPUP_TEXT_OFFSET;
296 Image tailImage = Image::New( DEFAULT_POPUP_TAIL_BOTTOM );
298 mTail = ImageActor::New( tailImage );
299 mTail.SetParentOrigin( ParentOrigin::BOTTOM_CENTER );
300 mTail.SetAnchorPoint( AnchorPoint::TOP_CENTER );
301 mBackground.Add( mTail );
302 // TODO: Make tail visible, and positioned in relation to original intended position of popup (i.e. before constrained effects)
303 mTail.SetVisible(false);
307 void TextInputPopup::CreateDivider()
309 if(mButtonContainer.size() > 0)
311 Image dividerImage = Image::New( DEFAULT_PANEL_BUTTON_DIVIDER );
312 ImageActor divider = ImageActor::New( dividerImage );
313 divider.SetParentOrigin( ParentOrigin::TOP_LEFT );
314 divider.SetAnchorPoint( AnchorPoint::TOP_LEFT );
315 divider.SetPosition( Vector3( mContentSize.width, POPUP_TEXT_OFFSET.y + 5.0f, 0.0f ) );
316 // Keep track of all the dividers. As their height's need to be updated to the max. of all
317 // buttons currently added.
318 mDividerContainer.push_back(divider);
320 mBackground.Add( divider );
321 mContentSize.width += DIVIDER_WIDTH;
325 ImageActor TextInputPopup::CreatePressedBackground( const Vector3 requiredSize, const bool finalFlag )
327 std::string pressedImageFilename;
328 Vector4 pressedImageBorder;
329 Vector2 pressedImageSize;
331 if(mButtonContainer.size() == 0) // LEFT
333 pressedImageFilename = DEFAULT_BUTTON_HIGHLIGHT_LEFT;
334 pressedImageBorder = DEFAULT_BUTTON_HIGHLIGHT_LEFT_BORDER;
336 else if(!finalFlag) // CENTER
338 pressedImageFilename = DEFAULT_BUTTON_HIGHLIGHT_CENTER;
339 pressedImageBorder = DEFAULT_BUTTON_HIGHLIGHT_CENTER_BORDER;
343 pressedImageFilename = DEFAULT_BUTTON_HIGHLIGHT_RIGHT;
344 pressedImageBorder = DEFAULT_BUTTON_HIGHLIGHT_RIGHT_BORDER;
347 Image pressedImage = Image::New( pressedImageFilename );
348 ImageActor pressedImageBg = ImageActor::New( pressedImage );
349 pressedImageBg.SetStyle( ImageActor::STYLE_NINE_PATCH );
350 pressedImageBg.SetNinePatchBorder( pressedImageBorder );
351 pressedImageBg.SetSize ( requiredSize );
352 pressedImageBg.SetParentOrigin( ParentOrigin::CENTER );
353 pressedImageBg.SetAnchorPoint( AnchorPoint::CENTER );
355 return pressedImageBg;
358 void TextInputPopup::AddOption(const std::string& name, const std::string& caption, const Image iconImage, bool finalOption)
360 CreatePopUpBackground();
364 // Create a Button with Text, Icon and highlight when pressed
366 Toolkit::PushButton button = Toolkit::PushButton::New();
367 button.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
368 button.SetName( name );
370 // Create container for text and icon when not pressed
371 Actor iconTextContainer = Actor::New();
372 iconTextContainer.SetParentOrigin( ParentOrigin::TOP_LEFT );
373 iconTextContainer.SetAnchorPoint( AnchorPoint::TOP_LEFT );
377 style.SetFontPointSize( PointSize( DEFAULT_UI_FONT_SIZE ) );
378 MarkupProcessor::StyledTextArray styledCaption;
379 styledCaption.push_back( MarkupProcessor::StyledText( Text( caption ), style ) );
380 Toolkit::TextView label = CreateOptionText( styledCaption );
381 label.SetName( name );
383 iconTextContainer.Add( label );
385 // Get natural size of text and then constrain it to bounds.
386 const Vector3 textSize = label.GetNaturalSize();
387 const Vector3 constrainedTextSize = Min( textSize, TEXT_LABEL_MAX_SIZE );
388 Vector3 buttonSize( Max(constrainedTextSize + BUTTON_TEXT_ENLARGE, BUTTON_TEXT_MINIMUM_SIZE) );
389 buttonSize = ( Min(buttonSize, BUTTON_TEXT_MAXIMUM_SIZE) );
390 label.SetSize( Min( buttonSize + BUTTON_TEXT_ENLARGE, constrainedTextSize ) );
392 button.SetParentOrigin( ParentOrigin::TOP_LEFT );
393 button.SetAnchorPoint( AnchorPoint::TOP_LEFT );
394 button.SetSize( buttonSize );
395 button.SetPosition( Vector3( mContentSize.width, POPUP_TEXT_OFFSET.y, 0.0f ) );
398 ImageActor icon = CreateOptionIcon( iconImage );
400 iconTextContainer.Add( icon );
402 // 3. Add highlight - Pressed state in Pushbutton needs a new image which means creating the text and icon again but including a highlight this time.
403 ImageActor pressedImageBg = CreatePressedBackground( buttonSize, finalOption );
405 Actor iconPressedTextContainer = Actor::New();
406 iconPressedTextContainer.SetDrawMode( DrawMode::OVERLAY );
408 Toolkit::TextView pressedLabel = CreateOptionText( styledCaption );
409 pressedLabel.SetSize( Min( buttonSize, TEXT_LABEL_MAX_SIZE ) );
410 ImageActor pressedIcon = CreateOptionIcon( iconImage );
412 iconPressedTextContainer.Add( pressedImageBg );
413 iconPressedTextContainer.Add( pressedLabel );
414 iconPressedTextContainer.Add( pressedIcon );
416 // Set Pressed button Image
417 iconPressedTextContainer.SetSize( buttonSize );
418 button.SetPressedImage( iconPressedTextContainer );
420 // Set Normal button Image
421 iconTextContainer.SetSize( buttonSize );
422 button.SetButtonImage( iconTextContainer );
423 mBackground.Add( button );
425 // Update content size (represents size of all content i.e. from top-left of first button, to bottom-right of last button)
426 mContentSize.width += buttonSize.width;
427 mContentSize.height = std::max(mContentSize.height, buttonSize.height);
428 mButtonContainer.push_back(button);
430 // resize all dividers based on the height content (i.e. max of all button heights)
431 const float dividerHeight = mContentSize.height - DIVIDER_MARGIN;
432 for(ActorIter i = mDividerContainer.begin(); i != mDividerContainer.end(); ++i)
434 i->SetSize( DIVIDER_WIDTH, dividerHeight );
437 Vector3 popupSize( Max(mContentSize + POPUP_TEXT_ENLARGE, POPUP_MINIMUM_SIZE) );
439 mBackground.SetSize( popupSize );
440 // Make Root Actor reflect the size of its content
441 mRootActor.SetSize( popupSize );
442 mTail.SetPosition(Vector3(0.0f, -20.0f, 0.0f));
444 button.ClickedSignal().Connect( this, &TextInputPopup::OnButtonPressed );
447 void TextInputPopup::Hide(bool animate)
459 mAnimation = Animation::New( HIDE_POPUP_ANIMATION_DURATION );
460 mAnimation.AnimateTo( Property(mBackground, Actor::SCALE), Vector3::ZERO, AlphaFunctions::EaseOut );
461 mAnimation.AnimateTo( Property(mBackground, Actor::COLOR_ALPHA), 0.0f, AlphaFunctions::EaseOut );
464 mAnimation.FinishedSignal().Connect( this, &TextInputPopup::OnHideFinished );
465 mState = StateHiding;
469 mBackground.SetProperty(Actor::SCALE, Vector3::ZERO);
470 mBackground.SetProperty(Actor::COLOR_ALPHA, 0.0f);
471 mState = StateHidden;
476 void TextInputPopup::Show(bool animate)
488 mAnimation = Animation::New( SHOW_POPUP_ANIMATION_DURATION );
489 mAnimation.AnimateTo( Property(mBackground, Actor::SCALE), Vector3::ONE, AlphaFunctions::EaseOut );
490 mAnimation.AnimateTo( Property(mBackground, Actor::COLOR_ALPHA), 1.0f, AlphaFunctions::EaseOut );
493 mAnimation.FinishedSignal().Connect( this, &TextInputPopup::OnShowFinished );
494 mState = StateShowing;
498 mBackground.SetProperty(Actor::SCALE, Vector3::ONE);
499 mBackground.SetProperty(Actor::COLOR_ALPHA, 1.0f);
505 void TextInputPopup::SetAlternativeOffset(Vector2 offset)
507 mRootActor.SetProperty( mAlternativeOffsetProperty, offset );
508 ApplyConfinementConstraint();
511 TextInputPopup::State TextInputPopup::GetState(void) const
516 Actor TextInputPopup::GetRootActor() const
521 bool TextInputPopup::OnButtonPressed( Toolkit::Button button )
523 mPressedSignal.Emit( button );
527 void TextInputPopup::OnHideFinished(Animation& source)
529 source.FinishedSignal().Disconnect( this, &TextInputPopup::OnHideFinished );
531 mState = StateHidden;
532 mHideFinishedSignal.Emit( *this );
535 void TextInputPopup::OnShowFinished(Animation& source)
537 source.FinishedSignal().Disconnect( this, &TextInputPopup::OnShowFinished );
539 mShowFinishedSignal.Emit( *this );
542 TextInputPopup::PressedSignalV2& TextInputPopup::PressedSignal()
544 return mPressedSignal;
547 TextInputPopup::HideFinishedSignalV2& TextInputPopup::HideFinishedSignal()
549 return mHideFinishedSignal;
552 TextInputPopup::ShowFinishedSignalV2& TextInputPopup::ShowFinishedSignal()
554 return mShowFinishedSignal;
557 } // namespace Internal
559 } // namespace Toolkit