-using System;
+/*
+ * Copyright(c) 2022 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
using System.ComponentModel;
+using System.Diagnostics;
using Tizen.NUI.BaseComponents;
using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility; // To use AccessibilityManager
namespace Tizen.NUI.Components
{
private ImageView overlayImage;
private TextLabel buttonText;
private ImageView buttonIcon;
+ private Vector2 size;
- private EventHandler<StateChangedEventArgs> stateChangeHander;
+ private EventHandler<StateChangedEventArgs> stateChangeHandler;
- private bool isSelected = false;
- private bool isEnabled = true;
private bool isPressed = false;
+ internal int styleApplying = 0;
- private StringSelector textSelector = new StringSelector();
- private StringSelector translatableTextSelector = new StringSelector();
- private ColorSelector textColorSelector = new ColorSelector();
- private FloatSelector pointSizeSelector = new FloatSelector();
-
- private StringSelector iconURLSelector = new StringSelector();
+ /// <summary>
+ /// Gets accessibility name.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override string AccessibilityGetName()
+ {
+ return Text;
+ }
/// <summary>
/// The ButtonExtension instance that is injected by ButtonStyle.
[EditorBrowsable(EditorBrowsableState.Never)]
protected virtual TextLabel CreateText()
{
- return new TextLabel();
+ return new TextLabel(new TextLabelStyle())
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ AccessibilityHidden = true,
+ };
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
protected virtual ImageView CreateIcon()
{
- return new ImageView();
+ return new ImageView()
+ {
+ AccessibilityHidden = true,
+ };
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
protected virtual ImageView CreateOverlayImage()
{
- return new ImageView();
+ return new ImageView
+ {
+ PositionUsesPivotPoint = true,
+ ParentOrigin = NUI.ParentOrigin.Center,
+ PivotPoint = NUI.PivotPoint.Center,
+ WidthResizePolicy = ResizePolicyType.FillToParent,
+ HeightResizePolicy = ResizePolicyType.FillToParent,
+ AccessibilityHidden = true,
+ };
}
/// <summary>
/// </summary>
/// <param name="eventArgs">The click information.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
- protected virtual void OnClick(ClickEventArgs eventArgs)
+ protected virtual void OnClicked(ClickedEventArgs eventArgs)
{
}
protected override void OnUpdate()
{
base.OnUpdate();
- UpdateUIContent();
-
Extension?.OnRelayout(this);
}
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override bool HandleControlStateOnTouch(Touch touch)
+ {
+ if (!IsEnabled || null == touch)
+ {
+ return false;
+ }
+
+ PointStateType state = touch.GetState(0);
+
+ switch (state)
+ {
+ case PointStateType.Down:
+ isPressed = true;
+ Extension?.SetTouchInfo(touch);
+ UpdateState();
+ return true;
+ case PointStateType.Interrupted:
+ isPressed = false;
+ UpdateState();
+ return true;
+ case PointStateType.Up:
+ {
+ if (!isPressed)
+ {
+ return false;
+ }
+
+ isPressed = false;
+
+ if (IsSelectable)
+ {
+ Extension?.SetTouchInfo(touch);
+ IsSelected = !IsSelected;
+ }
+ else
+ {
+ Extension?.SetTouchInfo(touch);
+ UpdateState();
+ }
+
+ ClickedEventArgs eventArgs = new ClickedEventArgs();
+ OnClickedInternal(eventArgs, touch);
+
+ return true;
+ }
+ default:
+ break;
+ }
+ return base.HandleControlStateOnTouch(touch);
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnEnabled(bool enabled)
+ {
+ base.OnEnabled(enabled);
+ UpdateState();
+ }
+
/// <summary>
/// Update Button State.
/// </summary>
- /// <param name="touchInfo">The touch information in case the state has changed by touching.</param>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void UpdateState()
{
+ if (styleApplying > 0) return;
+
ControlState sourceState = ControlState;
ControlState targetState;
PreviousState = ControlStatesExtension.FromControlStateClass(sourceState),
CurrentState = ControlStatesExtension.FromControlStateClass(targetState)
};
- stateChangeHander?.Invoke(this, e);
+ stateChangeHandler?.Invoke(this, e);
Extension?.OnControlStateChanged(this, new ControlStateChangedEventArgs(sourceState, targetState));
}
}
/// <summary>
- /// Measure text, it can be override.
+ /// Dispose Button and all children on it.
/// </summary>
+ /// <param name="type">Dispose type.</param>
/// <since_tizen> 6 </since_tizen>
- /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
- [EditorBrowsable(EditorBrowsableState.Never)]
- protected virtual void MeasureText()
+ protected override void Dispose(DisposeTypes type)
{
- if (Style.IconRelativeOrientation == null || Icon == null || TextLabel == null)
+ if (disposed)
{
return;
}
- TextLabel.WidthResizePolicy = ResizePolicyType.Fixed;
- TextLabel.HeightResizePolicy = ResizePolicyType.Fixed;
- int textPaddingStart = Style.TextPadding.Start;
- int textPaddingEnd = Style.TextPadding.End;
- int textPaddingTop = Style.TextPadding.Top;
- int textPaddingBottom = Style.TextPadding.Bottom;
-
- int iconPaddingStart = Style.IconPadding.Start;
- int iconPaddingEnd = Style.IconPadding.End;
- int iconPaddingTop = Style.IconPadding.Top;
- int iconPaddingBottom = Style.IconPadding.Bottom;
-
- if (IconRelativeOrientation == IconOrientation.Top || IconRelativeOrientation == IconOrientation.Bottom)
- {
- TextLabel.SizeWidth = SizeWidth - textPaddingStart - textPaddingEnd;
- TextLabel.SizeHeight = SizeHeight - textPaddingTop - textPaddingBottom - iconPaddingTop - iconPaddingBottom - Icon.SizeHeight;
- }
- else
+
+ if (type == DisposeTypes.Explicit)
{
- TextLabel.SizeWidth = SizeWidth - textPaddingStart - textPaddingEnd - iconPaddingStart - iconPaddingEnd - Icon.SizeWidth;
- TextLabel.SizeHeight = SizeHeight - textPaddingTop - textPaddingBottom;
+ Extension?.OnDispose(this);
+
+ if (buttonIcon != null)
+ {
+ Utility.Dispose(buttonIcon);
+ }
+ if (buttonText != null)
+ {
+ Utility.Dispose(buttonText);
+ }
+ if (overlayImage != null)
+ {
+ Utility.Dispose(overlayImage);
+ }
}
+
+ base.Dispose(type);
}
/// <summary>
- /// Layout child, it can be override.
+ /// Initializes AT-SPI object.
/// </summary>
- /// <since_tizen> 6 </since_tizen>
- /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- protected virtual void LayoutChild()
+ public override void OnInitialize()
+ {
+ base.OnInitialize();
+
+ AccessibilityRole = Role.PushButton;
+ AccessibilityHighlightable = true;
+ EnableControlStatePropagation = true;
+
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "Button");
+
+ buttonText = CreateText();
+ buttonIcon = CreateIcon();
+ LayoutItems();
+
+ Feedback = true;
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnRelayout(Vector2 size, RelayoutContainer container)
{
- if (Style.IconRelativeOrientation == null || Icon == null || TextLabel == null)
+ if (size == null) return;
+
+ if (size.Equals(this.size))
{
return;
}
- var buttonIcon = Icon;
- var buttonText = TextLabel;
+ this.size = new Vector2(size);
- int textPaddingStart = Style.TextPadding.Start;
- int textPaddingEnd = Style.TextPadding.End;
- int textPaddingTop = Style.TextPadding.Top;
- int textPaddingBottom = Style.TextPadding.Bottom;
+ UpdateSizeAndSpacing();
+ }
- int iconPaddingStart = Style.IconPadding.Start;
- int iconPaddingEnd = Style.IconPadding.End;
- int iconPaddingTop = Style.IconPadding.Top;
- int iconPaddingBottom = Style.IconPadding.Bottom;
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnControlStateChanged(ControlStateChangedEventArgs controlStateChangedInfo)
+ {
+ base.OnControlStateChanged(controlStateChangedInfo);
- switch (IconRelativeOrientation)
- {
- case IconOrientation.Top:
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.TopCenter;
- buttonIcon.PivotPoint = NUI.PivotPoint.TopCenter;
- buttonIcon.Position2D = new Position2D(0, iconPaddingTop);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.BottomCenter;
- buttonText.PivotPoint = NUI.PivotPoint.BottomCenter;
- buttonText.Position2D = new Position2D(0, -textPaddingBottom);
- break;
- case IconOrientation.Bottom:
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.BottomCenter;
- buttonIcon.PivotPoint = NUI.PivotPoint.BottomCenter;
- buttonIcon.Position2D = new Position2D(0, -iconPaddingBottom);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.TopCenter;
- buttonText.PivotPoint = NUI.PivotPoint.TopCenter;
- buttonText.Position2D = new Position2D(0, textPaddingTop);
- break;
- case IconOrientation.Left:
- if (LayoutDirection == ViewLayoutDirectionType.LTR)
- {
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.CenterLeft;
- buttonIcon.PivotPoint = NUI.PivotPoint.CenterLeft;
- buttonIcon.Position2D = new Position2D(iconPaddingStart, 0);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.CenterRight;
- buttonText.PivotPoint = NUI.PivotPoint.CenterRight;
- buttonText.Position2D = new Position2D(-textPaddingEnd, 0);
- }
- else
- {
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.CenterRight;
- buttonIcon.PivotPoint = NUI.PivotPoint.CenterRight;
- buttonIcon.Position2D = new Position2D(-iconPaddingStart, 0);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.CenterLeft;
- buttonText.PivotPoint = NUI.PivotPoint.CenterLeft;
- buttonText.Position2D = new Position2D(textPaddingEnd, 0);
- }
+ var stateEnabled = !controlStateChangedInfo.CurrentState.Contains(ControlState.Disabled);
- break;
- case IconOrientation.Right:
- if (LayoutDirection == ViewLayoutDirectionType.RTL)
- {
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.CenterLeft;
- buttonIcon.PivotPoint = NUI.PivotPoint.CenterLeft;
- buttonIcon.Position2D = new Position2D(iconPaddingEnd, 0);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.CenterRight;
- buttonText.PivotPoint = NUI.PivotPoint.CenterRight;
- buttonText.Position2D = new Position2D(-textPaddingStart, 0);
- }
- else
- {
- buttonIcon.PositionUsesPivotPoint = true;
- buttonIcon.ParentOrigin = NUI.ParentOrigin.CenterRight;
- buttonIcon.PivotPoint = NUI.PivotPoint.CenterRight;
- buttonIcon.Position2D = new Position2D(-iconPaddingEnd, 0);
-
- buttonText.PositionUsesPivotPoint = true;
- buttonText.ParentOrigin = NUI.ParentOrigin.CenterLeft;
- buttonText.PivotPoint = NUI.PivotPoint.CenterLeft;
- buttonText.Position2D = new Position2D(textPaddingStart, 0);
- }
- break;
- default:
- break;
+ if (IsEnabled != stateEnabled)
+ {
+ IsEnabled = stateEnabled;
}
- if (string.IsNullOrEmpty(buttonText.Text))
+
+ var statePressed = controlStateChangedInfo.CurrentState.Contains(ControlState.Pressed);
+
+ if (isPressed != statePressed)
{
- buttonIcon.ParentOrigin = NUI.ParentOrigin.Center;
- buttonIcon.PivotPoint = NUI.PivotPoint.Center;
+ isPressed = statePressed;
}
- }
- /// <summary>
- /// Theme change callback when theme is changed, this callback will be trigger.
- /// </summary>
- /// <param name="sender">The sender</param>
- /// <param name="e">The event data</param>
- [EditorBrowsable(EditorBrowsableState.Never)]
- protected override void OnThemeChangedEvent(object sender, StyleManager.ThemeChangeEventArgs e)
- {
- ButtonStyle buttonStyle = StyleManager.Instance.GetViewStyle(StyleName) as ButtonStyle;
- if (buttonStyle != null)
+ if (IsSelectable)
{
- Style.CopyFrom(buttonStyle);
- UpdateUIContent();
+ var stateSelected = controlStateChangedInfo.CurrentState.Contains(ControlState.Selected);
+
+ if (IsSelected != stateSelected)
+ {
+ IsSelected = stateSelected;
+ }
}
}
/// <summary>
- /// Dispose Button and all children on it.
+ /// Put sub items (e.g. buttonText, buttonIcon) to the right place.
/// </summary>
- /// <param name="type">Dispose type.</param>
- /// <since_tizen> 6 </since_tizen>
- protected override void Dispose(DisposeTypes type)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void LayoutItems()
{
- if (disposed)
+ if (buttonIcon == null || buttonText == null)
{
return;
}
- if (type == DisposeTypes.Explicit)
+ buttonIcon.Unparent();
+ buttonText.Unparent();
+ overlayImage?.Unparent();
+
+#pragma warning disable CA2000
+ Size2D cellPadding = String.IsNullOrEmpty(buttonText.Text) ? new Size2D(0, 0) : itemSpacing;
+#pragma warning restore CA2000
+
+ if (IconRelativeOrientation == IconOrientation.Left)
{
- Extension?.OnDispose(this);
+ Layout = new LinearLayout()
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ HorizontalAlignment = itemHorizontalAlignment,
+ VerticalAlignment = itemVerticalAlignment,
+ CellPadding = cellPadding
+ };
- if (Icon != null)
+ Add(buttonIcon);
+ Add(buttonText);
+ }
+ else if (IconRelativeOrientation == IconOrientation.Right)
+ {
+ Layout = new LinearLayout()
{
- Utility.Dispose(Icon);
- }
- if (TextLabel != null)
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ HorizontalAlignment = itemHorizontalAlignment,
+ VerticalAlignment = itemVerticalAlignment,
+ CellPadding = cellPadding
+ };
+
+ Add(buttonText);
+ Add(buttonIcon);
+ }
+ else if (IconRelativeOrientation == IconOrientation.Top)
+ {
+ Layout = new LinearLayout()
{
- Utility.Dispose(TextLabel);
- }
- if (OverlayImage != null)
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ HorizontalAlignment = itemHorizontalAlignment,
+ VerticalAlignment = itemVerticalAlignment,
+ CellPadding = cellPadding
+ };
+
+ Add(buttonIcon);
+ Add(buttonText);
+ }
+ else if (IconRelativeOrientation == IconOrientation.Bottom)
+ {
+ Layout = new LinearLayout()
{
- Utility.Dispose(OverlayImage);
- }
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ HorizontalAlignment = itemHorizontalAlignment,
+ VerticalAlignment = itemVerticalAlignment,
+ CellPadding = cellPadding
+ };
+
+ Add(buttonText);
+ Add(buttonIcon);
}
- base.Dispose(type);
+ if (overlayImage != null)
+ {
+ overlayImage.ExcludeLayouting = true;
+ Add(overlayImage);
+ }
}
- /// <inheritdoc/>
- [EditorBrowsable(EditorBrowsableState.Never)]
- protected override void OnControlStateChanged(ControlStateChangedEventArgs controlStateChangedInfo)
+ private void UpdateSizeAndSpacing()
{
- base.OnControlStateChanged(controlStateChangedInfo);
+ if (size == null || buttonIcon == null || buttonText == null)
+ {
+ return;
+ }
- var stateEnabled = !controlStateChangedInfo.CurrentState.Contains(ControlState.Disabled);
+ LinearLayout layout = Layout as LinearLayout;
- if (isEnabled != stateEnabled)
+ if (layout == null)
{
- isEnabled = stateEnabled;
+ return;
}
- var statePressed = controlStateChangedInfo.CurrentState.Contains(ControlState.Pressed);
+ float lengthWithoutText = 0;
+ Size2D cellPadding = null;
+ Extents iconMargin = buttonIcon.Margin ?? new Extents(0);
+ Extents textMargin = buttonText.Margin ?? new Extents(0);
- if (isPressed != statePressed)
+ if (buttonIcon.Size.Width != 0 && buttonIcon.Size.Height != 0)
{
- isPressed = statePressed;
- }
- }
+ lengthWithoutText = buttonIcon.Size.Width;
- /// <summary>
- /// It is hijack by using protected, style copy problem when class inherited from Button.
- /// </summary>
- /// <since_tizen> 6 </since_tizen>
- private void Initialize()
- {
- var style = (ButtonStyle)Style;
- EnableControlStatePropagation = true;
- UpdateState();
- LayoutDirectionChanged += OnLayoutDirectionChanged;
- }
+ if (!String.IsNullOrEmpty(buttonText.Text))
+ {
+ cellPadding = itemSpacing;
- private void UpdateUIContent()
- {
- MeasureText();
- LayoutChild();
+ if (iconRelativeOrientation == IconOrientation.Left || iconRelativeOrientation == IconOrientation.Right)
+ {
+ lengthWithoutText += (itemSpacing?.Width ?? 0) + iconMargin.Start + iconMargin.End + textMargin.Start + textMargin.End + Padding.Start + Padding.End;
+ }
+ else
+ {
+ lengthWithoutText += (itemSpacing?.Height ?? 0) + iconMargin.Top + iconMargin.Bottom + textMargin.Top + textMargin.Bottom + Padding.Top + Padding.Bottom;
+ }
+ }
+ }
- Sensitive = isEnabled;
+ layout.CellPadding = cellPadding ?? new Size2D(0, 0);
+
+ // If the button has fixed width and the text is not empty, the text should not exceed button boundary.
+ if (WidthSpecification != LayoutParamPolicies.WrapContent && !String.IsNullOrEmpty(buttonText.Text))
+ {
+ buttonText.MaximumSize = new Size2D((int)Math.Max(size.Width - lengthWithoutText, Math.Max(buttonText.MinimumSize.Width, 1)), (int)size.Height);
+ }
}
- private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+ private void OnClickedInternal(ClickedEventArgs eventArgs, Touch touch)
{
- MeasureText();
- LayoutChild();
+ // If GrabTouchAfterLeave is true, Up will result in Finished rather than Interrupted even if it is out of the button area.
+ // So, it is necessary to check whether it is Up in the button area.
+ if (GrabTouchAfterLeave == true)
+ {
+ Vector2 localPosition = touch.GetLocalPosition(0);
+ if ((localPosition != null && Size != null &&
+ 0 <= localPosition.X && localPosition.X <= Size.Width &&
+ 0 <= localPosition.Y && localPosition.Y <= Size.Height) == false)
+ {
+ return;
+ }
+ }
+ OnClickedInternal(eventArgs);
}
- private void OnClickInternal(ClickEventArgs eventArgs)
+ private void OnClickedInternal(ClickedEventArgs eventArgs)
{
Command?.Execute(CommandParameter);
- OnClick(eventArgs);
- Extension?.OnClick(this, eventArgs);
- ClickEvent?.Invoke(this, eventArgs);
- }
+ OnClicked(eventArgs);
+ Extension?.OnClicked(this, eventArgs);
- private void OnIconRelayout(object sender, EventArgs e)
- {
- MeasureText();
- LayoutChild();
+ ClickEventArgs nestedEventArgs = new ClickEventArgs();
+ ClickEvent?.Invoke(this, nestedEventArgs);
+ Clicked?.Invoke(this, eventArgs);
}
-
}
}