public ElmSharp.Wearable.CircleSurface CircleSurface;
+ public void UpdateRotaryFocusObject()
+ {
+ _circlePage.UpdateRotaryFocusObject(Element.RotaryFocusObject);
+ }
+
protected override void OnElementChanged(ElementChangedEventArgs<CirclePage> e)
{
if (_circlePage == null)
}
base.OnElementChanged(e);
}
+
+ protected override void OnElementReady()
+ {
+ base.OnElementReady();
+ // A Page created by with ContentTemplate of ShellContent, was appered before create a renderer
+ // So need to call OnPageAppearing if page already appeared
+ if (Element.Appeared)
+ {
+ OnPageAppearing(Element, EventArgs.Empty);
+ }
+ }
+
protected override void UpdateBackgroundColor(bool initialize)
{
if (initialize && Element.BackgroundColor.IsDefault)
}
}
- void UpdateRotaryFocusObject()
- {
- _circlePage.UpdateRotaryFocusObject(Element.RotaryFocusObject);
- }
-
void OnPageDisappearing(object sender, EventArgs e)
{
_circlePage.DeactivateRotaryWidget();
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using ElmSharp;
+using ElmSharp.Wearable;
+using Tizen.Wearable.CircularUI.Forms;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using XForms = Xamarin.Forms.Forms;
+using ELayout = ElmSharp.Layout;
+
+[assembly: ExportRenderer(typeof(CircleSurfaceView), typeof(Tizen.Wearable.CircularUI.Forms.Renderer.CircleSurfaceViewRenderer))]
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class CircleSurfaceViewRenderer : ViewRenderer<CircleSurfaceView, Box>
+ {
+
+ Dictionary<ICircleSurfaceItem, ICircleWidget> _circleSurfaceItems;
+ ELayout _surfaceLayout;
+ CircleSurface _circleSurface;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<CircleSurfaceView> e)
+ {
+ if (Control == null)
+ {
+ var box = new Box(XForms.NativeParent);
+ box.SetLayoutCallback(OnLayout);
+ _surfaceLayout = new ELayout(box);
+ _circleSurface = new CircleSurface(_surfaceLayout);
+ _circleSurfaceItems = new Dictionary<ICircleSurfaceItem, ICircleWidget>();
+ box.PackEnd(_surfaceLayout);
+ _surfaceLayout.Show();
+ SetNativeControl(box);
+ }
+
+ if (e.NewElement != null)
+ {
+ var items = e.NewElement.CircleSurfaceItems as ObservableCollection<ICircleSurfaceItem>;
+ items.CollectionChanged += OnCircleSurfaceItemsChanged;
+ foreach (var item in items)
+ {
+ AddCircleSurfaceItem(item);
+ }
+ }
+
+ if (e.OldElement != null)
+ {
+ var items = e.OldElement.CircleSurfaceItems as ObservableCollection<ICircleSurfaceItem>;
+ foreach (var item in items)
+ {
+ RemoveCircleSurfaceItem(item);
+ }
+ items.CollectionChanged -= OnCircleSurfaceItemsChanged;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (Element != null)
+ {
+ var items = Element.CircleSurfaceItems as ObservableCollection<ICircleSurfaceItem>;
+ foreach (var item in items)
+ {
+ RemoveCircleSurfaceItem(item);
+ }
+ items.CollectionChanged -= OnCircleSurfaceItemsChanged;
+ }
+ base.Dispose(disposing);
+ }
+
+ void OnLayout()
+ {
+ var rect = Control.Geometry;
+ Element.Layout(rect.ToDP());
+ _surfaceLayout.Geometry = rect;
+ }
+
+ void OnCircleSurfaceItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.Action == NotifyCollectionChangedAction.Add ||
+ e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ foreach (ICircleSurfaceItem item in e.NewItems)
+ AddCircleSurfaceItem(item);
+ }
+ if (e.Action == NotifyCollectionChangedAction.Remove ||
+ e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ foreach (ICircleSurfaceItem item in e.OldItems)
+ RemoveCircleSurfaceItem(item);
+ }
+ }
+
+ void AddCircleSurfaceItem(ICircleSurfaceItem item)
+ {
+ if (item is CircleProgressBarSurfaceItem)
+ {
+ var widget = new CircleProgressBarSurfaceItemImplements(item as CircleProgressBarSurfaceItem, _surfaceLayout, _circleSurface);
+ _circleSurfaceItems[item] = widget;
+ }
+ else if (item is CircleSliderSurfaceItem)
+ {
+ var widget = new CircleSliderSurfaceItemImplements(item as CircleSliderSurfaceItem, _surfaceLayout, _circleSurface);
+ _circleSurfaceItems[item] = widget;
+ }
+ }
+
+ void RemoveCircleSurfaceItem(ICircleSurfaceItem item)
+ {
+ if (_circleSurfaceItems.TryGetValue(item, out var widget))
+ {
+ EvasObject obj = widget as EvasObject;
+ obj?.Unrealize();
+ _circleSurfaceItems.Remove(item);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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 Tizen.Wearable.CircularUI.Forms;
+using Tizen.Wearable.CircularUI.Forms.Renderer;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Xamarin.Forms.Platform.Tizen.Native;
+using XForms = Xamarin.Forms.Forms;
+using XFLayout = Xamarin.Forms.Layout;
+using EButton = ElmSharp.Button;
+using EColor = ElmSharp.Color;
+using System.ComponentModel;
+
+[assembly: ExportRenderer(typeof(ContentButton), typeof(ContentButtonRenderer))]
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ContentButtonRenderer : LayoutRenderer
+ {
+ EButton _button;
+
+ ContentButton Button => Element as ContentButton;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<XFLayout> e)
+ {
+ base.OnElementChanged(e);
+ Initialize();
+ }
+
+ void Initialize()
+ {
+ if (_button == null)
+ {
+ _button = new EButton(XForms.NativeParent);
+ _button.BackgroundColor = EColor.Transparent;
+ _button.SetPartColor("effect", EColor.Transparent);
+ _button.SetPartColor("effect_pressed", EColor.Transparent);
+ _button.Show();
+
+ _button.Pressed += OnPressed;
+ _button.Released += OnReleased;
+ _button.Clicked += OnClicked;
+
+ Control.PackEnd(_button);
+ }
+ }
+
+ protected override void UpdateLayout()
+ {
+ base.UpdateLayout();
+
+ _button.Geometry = Control.Geometry;
+ _button.RaiseTop();
+ }
+
+ void OnPressed(object sender, EventArgs args)
+ {
+ Button?.SendPressed();
+ }
+
+ void OnReleased(object sender, EventArgs args)
+ {
+ Button?.SendReleased();
+ }
+
+ void OnClicked(object sender, EventArgs args)
+ {
+ Button?.SendClicked();
+ }
+ }
+}
_box = null;
}
- if(_progress != null)
+ if (_progress != null)
{
_progress.Unrealize();
_progress = null;
void UpdateProcessVisibility()
{
- if(!XForms.IsInitialized)
+ if (!XForms.IsInitialized)
{
Log.Debug(FormsCircularUI.Tag, "Tizen Forms is not initialized");
return;
Style = "bottom"
};
- if (!string.IsNullOrEmpty(BottomButton.Text))_bottomButton.Text = BottomButton.Text;
+ if (!string.IsNullOrEmpty(BottomButton.Text)) _bottomButton.Text = BottomButton.Text;
if (!BottomButton.IconImageSource.IsNullOrEmpty())
{
void UpdateTitle()
{
+ string title = _title?.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace(Environment.NewLine, "<br>");
+
if (!_isProgressRunning)
{
- _layout.SetPartText("elm.text.title", _title);
+ _layout.SetPartText("elm.text.title", title);
}
else
{
void UpdateText()
{
+ string text = _text?.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace(Environment.NewLine, "<br>");
+
if (!_isProgressRunning)
{
- _layout.SetPartText("elm.text", _text);
+ _layout.SetPartText("elm.text", text);
}
else
{
_layout.SetPartText("elm.text", null);
- if (!string.IsNullOrEmpty(_text))
+ if (!string.IsNullOrEmpty(text))
{
if (_progressLabel == null)
{
TextStyle = "DEFAULT ='font=Tizen:style=Light color=#F9F9F9FF font_size=32 align=center valign=top wrap=word'",
};
}
- _progressLabel.Text = _text;
+ _progressLabel.Text = text;
_progressLabel.Show();
if (_box != null)
{
using ElmSharp;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Tizen;
-using Xamarin.Forms.Internals;
using XForms = Xamarin.Forms.Forms;
namespace Tizen.Wearable.CircularUI.Forms.Renderer
using Tizen.Multimedia;
using Tizen.Wearable.CircularUI.Forms.Renderer;
using Xamarin.Forms;
-using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.Tizen;
+using XForms = Xamarin.Forms.Forms;
[assembly: Xamarin.Forms.Dependency(typeof(MediaPlayerImpl))]
namespace Tizen.Wearable.CircularUI.Forms.Renderer
{
return;
}
- IMediaSourceHandler handler = global::Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IMediaSourceHandler>(_source);
+ IMediaSourceHandler handler = XForms.GetHandlerForObject<IMediaSourceHandler>(_source);
await handler.SetSource(_player, _source);
}
ApplyDisplay();
await ApplySource();
- try {
+ try
+ {
await _player.PrepareAsync();
UpdateStreamInfo?.Invoke(this, EventArgs.Empty);
_tcsForStreamInfo?.TrySetResult(true);
SetVisibleActionButton(ActionButton.IsVisible);
ActionButton.PropertyChanged += OnActionButtonItemChanged;
- _actionButton.Text = ActionButton.Text;
+ _actionButton.Text = ActionButton.Text?.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace(Environment.NewLine, "<br>");
_actionButton.IsEnabled = ActionButton.IsEnable;
if (!ActionButton.IconImageSource.IsNullOrEmpty())
{
buttonImage.Show();
_actionButton.SetPartContent("elm.swallow.content", buttonImage);
}
-
- if (ActionButton.BackgroundColor != Xamarin.Forms.Color.Default)
+ else
{
- _actionButton.BackgroundColor = ActionButton.BackgroundColor.ToNative();
+ _actionButton.SetPartContent("elm.swallow.content", null);
}
+
+ _actionButton.BackgroundColor = ActionButton.BackgroundColor.ToNative();
}
else
{
var btnW = Math.Max(_actionButton.MinimumWidth, btnRect.Width);
var btnH = Math.Max(_actionButton.MinimumHeight, btnRect.Height);
var btnX = rect.X + (rect.Width - btnW) / 2;
- var btnY = rect.Height - btnH;
+ var btnY = rect.Y + rect.Height - btnH;
_actionButton.Geometry = new Rect(btnX, btnY, btnW, btnH);
_actionButton.StackAbove(prev);
prev = _actionButton;
using ElmSharp;
using System;
+using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Tizen;
using Xamarin.Forms.Platform.Tizen.Native;
using EColor = ElmSharp.Color;
using ELayout = ElmSharp.Layout;
using XForms = Xamarin.Forms.Forms;
+using XEntry = Xamarin.Forms.Entry;
[assembly: ExportRenderer(typeof(Tizen.Wearable.CircularUI.Forms.PopupEntry), typeof(Tizen.Wearable.CircularUI.Forms.Renderer.PopupEntryRenderer))]
}
}
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (_IsPopupOpened && _editor != null)
+ {
+ if (e.PropertyName == XEntry.TextColorProperty.PropertyName ||
+ e.PropertyName == XEntry.FontSizeProperty.PropertyName ||
+ e.PropertyName == XEntry.FontFamilyProperty.PropertyName ||
+ e.PropertyName == XEntry.FontAttributesProperty.PropertyName)
+ {
+ _editor.TextStyle = Control.TextStyle;
+ }
+ }
+ }
+
protected override void Dispose(bool disposing)
{
if (disposing)
{
_popupBackgroundColor = bgColor.ToNative();
}
+
+ if (_IsPopupOpened == true && _editorPopup != null)
+ {
+ _editorPopup.Color = _popupBackgroundColor;
+ }
}
void OnTextChanged(object sender, EventArgs e)
--- /dev/null
+using System;
+using ElmSharp;
+using Xamarin.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public interface IShellItemRenderer : IDisposable
+ {
+ BaseShellItem Item { get; }
+ EvasObject NativeView { get; }
+ }
+}
--- /dev/null
+using ElmSharp;
+using ElmSharp.Wearable;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using EColor = ElmSharp.Color;
+using EImage = ElmSharp.Image;
+using ELayout = ElmSharp.Layout;
+using EWidget = ElmSharp.Widget;
+using EButton = ElmSharp.Button;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class NavigationDrawer : ELayout, IAnimatable
+ {
+ static readonly int TouchWidth = 50;
+ static readonly int IconSize = 40;
+ static readonly string DefaultIcon = "Tizen.Wearable.CircularUI.Forms.Renderer.res.wc_visual_cue.png";
+
+ Box _mainLayout;
+ Box _contentGestureBox;
+ Box _contentBox;
+ Box _drawerBox;
+ Box _drawerContentBox;
+ Box _drawerIconBox;
+
+ EvasObject _content;
+ EvasObject _drawerContent;
+
+ EImage _drawerIcon;
+ EButton _touchArea;
+
+ GestureLayer _gestureOnContent;
+ GestureLayer _gestureOnDrawer;
+
+ ImageSource _drawerIconSource;
+
+ bool _isOpen;
+ bool _isDefaultIcon;
+
+ CancellationTokenSource _fadeInCancelTokenSource = null;
+
+ bool HasDrawer => _drawerBox != null;
+
+ public NavigationDrawer(EvasObject parent) : base(parent)
+ {
+ Initialize();
+ }
+
+ public int HandlerHeight { get; set; } = 40;
+
+ public bool IsOpen
+ {
+ get
+ {
+ return _isOpen;
+ }
+ set
+ {
+ if (_isOpen != value)
+ {
+ if (value)
+ {
+ Open();
+ }
+ else
+ {
+ Close();
+ }
+ }
+ }
+ }
+
+ EColor _handlerBackgroundColor = EColor.Transparent;
+ public EColor HandlerBackgroundColor
+ {
+ get => _handlerBackgroundColor;
+ set
+ {
+ _handlerBackgroundColor = value;
+ UpdateHandlerBackgroundColor();
+ }
+ }
+
+ public event EventHandler Toggled;
+
+ public void SetMainContent(EvasObject content)
+ {
+ if (content == null)
+ {
+ UnsetMainContent();
+ return;
+ }
+
+ _content = content;
+ _content.Show();
+ _contentBox.PackEnd(_content);
+ _content.Geometry = _contentBox.Geometry;
+ }
+
+ public void SetDrawerContent(EvasObject content)
+ {
+ InitializeDrawerBox();
+
+ if (content == null)
+ {
+ UnsetDrawerContent();
+ return;
+ }
+
+ _drawerContent = content;
+ _drawerContent.Show();
+ _drawerContentBox.PackEnd(_drawerContent);
+
+ _drawerContentBox.Show();
+ _drawerIconBox.Show();
+
+ if (_drawerContent is NavigationView nv)
+ {
+ nv.Dragged += (s, e) =>
+ {
+ if (e.State == DraggedState.EdgeTop)
+ {
+ Close();
+ }
+ };
+ }
+ }
+
+ public void UpdateDrawerIcon(ImageSource source)
+ {
+ _drawerIconSource = source;
+ if (HasDrawer)
+ {
+ SetDrawerIcon(_drawerIconSource);
+ }
+ }
+
+ public async void Open(uint length = 300)
+ {
+ if (!HasDrawer)
+ return;
+
+ var toMove = _drawerBox.Geometry;
+ toMove.Y = 0;
+
+ await RunMoveAnimation(_drawerBox, toMove, length);
+
+ if (!_isOpen)
+ {
+ _isOpen = true;
+ Toggled?.Invoke(this, EventArgs.Empty);
+ }
+ OnLayout();
+ OnDrawerLayout();
+ FlipIcon();
+ }
+
+ public async void Close(uint length = 300)
+ {
+ if (!HasDrawer)
+ return;
+
+ var toMove = _drawerBox.Geometry;
+ toMove.Y = Geometry.Height - HandlerHeight;
+
+ await RunMoveAnimation(_drawerBox, toMove, length);
+
+ if (_isOpen)
+ {
+ _isOpen = false;
+ Toggled?.Invoke(this, EventArgs.Empty);
+ }
+ OnLayout();
+ OnDrawerLayout();
+ ResetIcon();
+ StartHighlightAnimation(_drawerIcon);
+ }
+
+ void IAnimatable.BatchBegin()
+ {
+ }
+
+ void IAnimatable.BatchCommit()
+ {
+ }
+
+ protected override IntPtr CreateHandle(EvasObject parent)
+ {
+ _mainLayout = new Box(parent);
+ return _mainLayout.Handle;
+ }
+
+ void Initialize()
+ {
+ _mainLayout.SetLayoutCallback(OnLayout);
+
+ _contentGestureBox = new Box(_mainLayout);
+ _contentGestureBox.Show();
+ _mainLayout.PackEnd(_contentGestureBox);
+
+ _contentBox = new Box(_mainLayout);
+ _contentBox.SetLayoutCallback(OnContentLayout);
+ _contentBox.Show();
+ _mainLayout.PackEnd(_contentBox);
+ }
+
+ void InitializeDrawerBox()
+ {
+ if (_drawerBox != null)
+ return;
+
+ _drawerBox = new Box(_mainLayout);
+ _drawerBox.SetLayoutCallback(OnDrawerLayout);
+ _drawerBox.Show();
+ _mainLayout.PackEnd(_drawerBox);
+
+ _drawerContentBox = new Box(_drawerBox);
+ _drawerBox.PackEnd(_drawerContentBox);
+
+ _drawerIconBox = new Box(_drawerBox)
+ {
+ BackgroundColor = _handlerBackgroundColor
+ };
+ _drawerBox.PackEnd(_drawerIconBox);
+
+ _drawerIcon = new EImage(_drawerIconBox)
+ {
+ AlignmentY = 0.5,
+ AlignmentX = 0.5,
+ MinimumHeight = IconSize,
+ MinimumWidth = IconSize,
+ };
+ _drawerIcon.Show();
+ _drawerIconBox.PackEnd(_drawerIcon);
+ SetDrawerIcon(_drawerIconSource);
+
+ _touchArea = new EButton(_drawerBox)
+ {
+ Color = EColor.Transparent,
+ BackgroundColor = EColor.Transparent,
+ };
+ _touchArea.SetPartColor("effect", EColor.Transparent);
+ _touchArea.Show();
+ _touchArea.RepeatEvents = true;
+ _touchArea.Clicked += OnIconClicked;
+
+ _drawerBox.PackEnd(_touchArea);
+
+ _gestureOnContent = new GestureLayer(_contentGestureBox);
+ _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.Start, OnContentDragStarted);
+ _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.End, OnContentDragEnded);
+ _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.Abort, OnContentDragEnded);
+ _gestureOnContent.Attach(_contentGestureBox);
+ _contentBox.RepeatEvents = true;
+
+ _gestureOnDrawer = new GestureLayer(_drawerIconBox);
+ _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.Move, OnDrawerDragged);
+ _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.End, OnDrawerDragEnded);
+ _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnDrawerDragEnded);
+ _gestureOnDrawer.Attach(_drawerIconBox);
+
+ RotaryEventManager.Rotated += OnRotateEventReceived;
+ }
+
+ void SetDrawerIcon(ImageSource source)
+ {
+ if (source == null)
+ {
+ _drawerIcon.LoadFromImageSourceAsync(ImageSource.FromResource(DefaultIcon, GetType().Assembly));
+ _isDefaultIcon = true;
+ }
+ else
+ {
+ _isDefaultIcon = false;
+ if (source is FileImageSource fsource)
+ {
+ _drawerIcon.Load(fsource.ToAbsPath());
+ }
+ else
+ {
+ _drawerIcon.LoadFromImageSourceAsync(source);
+ }
+ }
+ }
+
+ void UpdateHandlerBackgroundColor()
+ {
+ if (_drawerIconBox != null)
+ {
+ _drawerIconBox.BackgroundColor = _handlerBackgroundColor;
+ }
+ }
+
+ void OnIconClicked(object sender, EventArgs e)
+ {
+ if (IsOpen)
+ Close();
+ else
+ Open();
+ }
+
+ async Task<bool> ShowAsync(EWidget target, Easing easing = null, uint length = 300, CancellationToken cancelltaionToken = default(CancellationToken))
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ await Task.Delay(1000);
+
+ if (cancelltaionToken.IsCancellationRequested)
+ {
+ cancelltaionToken.ThrowIfCancellationRequested();
+ }
+
+ target.Show();
+ var opacity = target.Opacity;
+
+ if (opacity == 255 || opacity == -1)
+ return true;
+
+ new Animation((progress) =>
+ {
+ target.Opacity = opacity + (int)((255 - opacity) * progress);
+
+ }).Commit(this, "FadeIn", length: length, finished: (p, e) =>
+ {
+ target.Opacity = 255;
+ tcs.SetResult(true);
+ StartHighlightAnimation(_drawerIcon);
+ });
+
+ return await tcs.Task;
+ }
+
+ void OnLayout()
+ {
+ var bound = Geometry;
+ _contentGestureBox.Geometry = bound;
+ _contentBox.Geometry = bound;
+ if (_drawerBox != null)
+ {
+ bound.Y = _isOpen ? 0 : (bound.Height - HandlerHeight);
+ _drawerBox.Geometry = bound;
+ }
+ }
+
+ void OnContentLayout()
+ {
+ if (_content != null)
+ {
+ _content.Geometry = _contentBox.Geometry;
+ }
+ }
+
+ void OnDrawerLayout()
+ {
+ this.AbortAnimation("HighlightAnimation");
+
+ var bound = _drawerBox.Geometry;
+
+ var currentY = bound.Y;
+ var ratio = currentY / (double)(Geometry.Height - HandlerHeight);
+
+ var contentBound = bound;
+ contentBound.Y += (int)(HandlerHeight * ratio);
+ _drawerContentBox.Geometry = contentBound;
+
+ var drawerHandleBound = bound;
+ drawerHandleBound.Height = HandlerHeight;
+ _drawerIconBox.Geometry = drawerHandleBound;
+
+ var drawerTouchBound = drawerHandleBound;
+ drawerTouchBound.Width = TouchWidth;
+ drawerTouchBound.X = drawerHandleBound.X + (drawerHandleBound.Width - TouchWidth) / 2;
+ _touchArea.Geometry = drawerTouchBound;
+ }
+
+ async Task<bool> HideAsync(EWidget target, Easing easing = null, uint length = 300)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ var opacity = target.Opacity;
+ if (opacity == -1)
+ opacity = 255;
+
+ new Animation((progress) =>
+ {
+ target.Opacity = opacity - (int)(progress * opacity);
+
+ }).Commit(this, "FadeOut", length: length, finished: (p, e) =>
+ {
+ target.Opacity = 0;
+ target.Hide();
+ tcs.SetResult(true);
+ });
+
+ return await tcs.Task;
+ }
+
+ void StartHighlightAnimation(EWidget target)
+ {
+ if (!_isDefaultIcon || this.AnimationIsRunning("HighlightAnimation"))
+ return;
+
+ int count = 2;
+ var bound = target.Geometry;
+ var y = bound.Y;
+ var dy = bound.Y - bound.Height / 3;
+
+ var anim = new Animation();
+
+ var transfAnim = new Animation((f) =>
+ {
+ bound.Y = (int)f;
+ var map = new EvasMap(4);
+ map.PopulatePoints(bound, 0);
+ target.IsMapEnabled = true;
+ target.EvasMap = map;
+ }, y, dy);
+
+ var opacityAnim = new Animation(f => target.Opacity = (int)f, 255, 40);
+
+ anim.Add(0, 1, opacityAnim);
+ anim.Add(0, 1, transfAnim);
+
+ anim.Commit(this, "HighlightAnimation", 16, 800, finished: (f, b) =>
+ {
+ target.Opacity = 255;
+ target.IsMapEnabled = false;
+ }, repeat:() => --count > 0);
+ }
+
+ async void OnRotateEventReceived(EventArgs args)
+ {
+ _fadeInCancelTokenSource?.Cancel();
+ _fadeInCancelTokenSource = new CancellationTokenSource();
+
+ if (!_isOpen)
+ {
+ var token = _fadeInCancelTokenSource.Token;
+ await HideAsync(_drawerBox);
+ _ = ShowAsync(_drawerBox, cancelltaionToken: token);
+ }
+ }
+
+ void OnContentDragStarted(GestureLayer.MomentumData moment)
+ {
+ _fadeInCancelTokenSource?.Cancel();
+ _fadeInCancelTokenSource = null;
+
+ if (!_isOpen)
+ {
+ _ = HideAsync(_drawerBox);
+ }
+ }
+
+ void OnContentDragEnded(GestureLayer.MomentumData moment)
+ {
+ _fadeInCancelTokenSource = new CancellationTokenSource();
+ _ = ShowAsync(_drawerBox, cancelltaionToken: _fadeInCancelTokenSource.Token);
+ }
+
+ void OnDrawerDragged(GestureLayer.MomentumData moment)
+ {
+ var toMove = _drawerBox.Geometry;
+ toMove.Y = (moment.Y2 < 0) ? 0 : moment.Y2;
+ _drawerBox.Geometry = toMove;
+ OnDrawerLayout();
+ }
+
+ void OnDrawerDragEnded(GestureLayer.MomentumData moment)
+ {
+ if (_drawerBox.Geometry.Y < (_mainLayout.Geometry.Height / 2))
+ {
+ Open();
+ }
+ else
+ {
+ Close();
+ }
+ }
+
+ void FlipIcon()
+ {
+ if (_isDefaultIcon)
+ {
+ _drawerIcon.Orientation = ImageOrientation.FlipVertical;
+ }
+ }
+
+ void ResetIcon()
+ {
+ _drawerIcon.Orientation = ImageOrientation.None;
+ }
+
+ Task RunMoveAnimation(EvasObject target, Rect dest, uint length, Easing easing = null)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ var dx = target.Geometry.X - dest.X;
+ var dy = target.Geometry.Y - dest.Y;
+
+ new Animation((progress) =>
+ {
+ var toMove = dest;
+ toMove.X += (int)(dx * (1 - progress));
+ toMove.Y += (int)(dy * (1 - progress));
+ target.Geometry = toMove;
+ OnDrawerLayout();
+ }).Commit(this, "Move", length: length, finished: (s, e) =>
+ {
+ target.Geometry = dest;
+ tcs.SetResult(true);
+ });
+ return tcs.Task;
+ }
+
+ void UnsetMainContent()
+ {
+ if (_content != null)
+ {
+ _contentBox.UnPack(_content);
+ _content.Hide();
+ _content = null;
+ }
+ }
+
+ void UnsetDrawerContent()
+ {
+ if (_drawerContent != null)
+ {
+ _drawerContentBox.UnPack(_drawerContent);
+ _drawerContent.Hide();
+ _drawerContent = null;
+
+ _drawerContentBox.Hide();
+ _drawerIconBox.Hide();
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using ElmSharp;
+using ElmSharp.Wearable;
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using ELayout = ElmSharp.Layout;
+using EColor = ElmSharp.Color;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class NavigationView : ELayout
+ {
+ readonly int _dafaultIconSize = 60;
+
+ class Item
+ {
+ public Element Source { get; set; }
+ public string Text { get; set; }
+ public string Icon { get; set; }
+ }
+
+ Box _outterBox;
+ ELayout _surfaceLayout;
+ CircleSurface _surface;
+ CircleGenList _naviMenu;
+
+ GenItemClass _defaultClass;
+ SmartEvent _draggedUpCallback;
+ SmartEvent _draggedDownCallback;
+
+ GenListItem _header;
+ GenListItem _footer;
+
+ List<List<Element>> _itemCache;
+ List<GenListItem> _items = new List<GenListItem>();
+
+ public NavigationView(EvasObject parent) : base(parent)
+ {
+ InitializeComponent();
+ }
+
+ public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
+
+ public event EventHandler<DraggedEventArgs> Dragged;
+
+
+ EColor _backgroundColor = EColor.Black;
+ public override EColor BackgroundColor
+ {
+ get => _backgroundColor;
+ set
+ {
+ _backgroundColor = value.IsDefault ? EColor.Black : value;
+ UpdateBackgroundColor();
+ }
+ }
+
+ EColor _foregroundColor = EColor.Default;
+ public EColor ForegroundColor
+ {
+ get => _foregroundColor;
+ set
+ {
+ _foregroundColor = value;
+ UpdateForegroundColor();
+ }
+ }
+
+
+ public void Build(List<List<Element>> items)
+ {
+ // Only update when items was changed
+ if (!IsUpdated(items))
+ {
+ return;
+ }
+ _itemCache = items;
+
+ _naviMenu.Clear();
+ _items.Clear();
+ // header
+ _header = _naviMenu.Append(_defaultClass, new Item { Text = "" });
+
+ // TODO. need to improve, need to support group
+ foreach (var group in items)
+ {
+ foreach (var item in group)
+ {
+ var data = new Item
+ {
+ Source = item
+ };
+ if (item is BaseShellItem shellItem)
+ {
+ data.Text = shellItem.Title;
+ data.Icon = (shellItem.Icon as FileImageSource)?.ToAbsPath();
+ }
+ else if (item is MenuItem menuItem)
+ {
+ data.Text = menuItem.Text;
+ data.Icon = (menuItem.IconImageSource as FileImageSource)?.ToAbsPath();
+ }
+ var genitem = _naviMenu.Append(_defaultClass, data, GenListItemType.Normal);
+ genitem.SetPartColor("bg", _backgroundColor);
+ _items.Add(genitem);
+ }
+ }
+ _footer = _naviMenu.Append(_defaultClass, new Item { Text = "" });
+ }
+
+ public void Activate()
+ {
+ (_naviMenu as IRotaryActionWidget)?.Activate();
+ }
+ public void Deactivate()
+ {
+ (_naviMenu as IRotaryActionWidget)?.Deactivate();
+ }
+
+ protected override IntPtr CreateHandle(EvasObject parent)
+ {
+ _outterBox = new Box(parent);
+ return _outterBox.Handle;
+ }
+
+ void InitializeComponent()
+ {
+ _outterBox.SetLayoutCallback(OnLayout);
+
+ _surfaceLayout = new ELayout(this);
+ _surfaceLayout.Show();
+ _surface = new CircleSurface(_surfaceLayout);
+
+ _naviMenu = new CircleGenList(this, _surface)
+ {
+ Homogeneous = true,
+ BackgroundColor = _backgroundColor
+ };
+ _naviMenu.Show();
+
+ _draggedUpCallback = new SmartEvent(_naviMenu, "drag,start,up");
+ _draggedUpCallback.On += (s, e) =>
+ {
+ if (_footer.TrackObject.IsVisible)
+ {
+ Dragged?.Invoke(this, new DraggedEventArgs(DraggedState.EdgeBottom));
+ }
+ else
+ {
+ Dragged?.Invoke(this, new DraggedEventArgs(DraggedState.Up));
+ }
+ };
+
+ _draggedDownCallback = new SmartEvent(_naviMenu, "drag,start,down");
+ _draggedDownCallback.On += (s, e) =>
+ {
+ if (_header.TrackObject.IsVisible)
+ {
+ Dragged?.Invoke(this, new DraggedEventArgs(DraggedState.EdgeTop));
+ }
+ else
+ {
+ Dragged?.Invoke(this, new DraggedEventArgs(DraggedState.Down));
+ }
+ };
+
+ _outterBox.PackEnd(_naviMenu);
+ _outterBox.PackEnd(_surfaceLayout);
+
+ _surfaceLayout.StackAbove(_naviMenu);
+
+ _defaultClass = new GenItemClass("1icon_1text")
+ {
+ GetTextHandler = (obj, part) =>
+ {
+ if (part == "elm.text")
+ {
+ var text = (obj as Item).Text;
+ if (_foregroundColor != EColor.Default)
+ return $"<span color='{_foregroundColor.ToHex()}'>{text}</span>";
+ else
+ return text;
+ }
+ return null;
+ },
+ GetContentHandler = (obj, part) =>
+ {
+ if (part == "elm.swallow.icon" && obj is Item menuItem && !string.IsNullOrEmpty(menuItem.Icon))
+ {
+ var icon = new ElmSharp.Image(Xamarin.Forms.Forms.NativeParent)
+ {
+ AlignmentX = -1,
+ AlignmentY = -1,
+ WeightX = 1.0,
+ WeightY = 1.0,
+ MinimumWidth = _dafaultIconSize,
+ MinimumHeight = _dafaultIconSize,
+ };
+ icon.Show();
+ icon.Load(menuItem.Icon);
+ return icon;
+ }
+ return null;
+ }
+ };
+
+ _naviMenu.ItemSelected += OnItemSelected;
+
+ }
+
+ void OnItemSelected(object sender, GenListItemEventArgs e)
+ {
+ ItemSelected?.Invoke(this, new SelectedItemChangedEventArgs((e.Item.Data as Item).Source, -1));
+ }
+
+ void OnLayout()
+ {
+ _surfaceLayout.Geometry = Geometry;
+ _naviMenu.Geometry = Geometry;
+ }
+
+ void UpdateBackgroundColor()
+ {
+ _naviMenu.BackgroundColor = _backgroundColor;
+ foreach (var item in _items)
+ {
+ item.SetPartColor("bg", _backgroundColor);
+ }
+ }
+
+ void UpdateForegroundColor()
+ {
+ foreach (var item in _items)
+ {
+ item.Update();
+ }
+ }
+
+ bool IsUpdated(List<List<Element>> items)
+ {
+ if (_itemCache == null)
+ return true;
+
+ if (_itemCache.Count != items.Count)
+ return true;
+
+ for (int i = 0; i < items.Count; i++)
+ {
+ if (_itemCache[i].Count != items[i].Count)
+ return true;
+
+ for (int j = 0; j < items[i].Count; j++)
+ {
+ if (_itemCache[i][j] != items[i][j])
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+ public enum DraggedState
+ {
+ EdgeTop,
+ Up,
+ Down,
+ EdgeBottom,
+ }
+
+ public class DraggedEventArgs
+ {
+ public DraggedState State { get; private set; }
+
+ public DraggedEventArgs(DraggedState state)
+ {
+ State = state;
+ }
+ }
+
+ static class FileImageSourceEX
+ {
+ public static string ToAbsPath(this FileImageSource source)
+ {
+ return ResourcePath.GetPath(source.File);
+ }
+ }
+
+ static class ColorEX
+ {
+ public static string ToHex(this EColor c)
+ {
+ return string.Format("#{0:X2}{1:X2}{2:X2}{3:X2}", c.R, c.G, c.B, c.A);
+ }
+ }
+}
--- /dev/null
+using ElmSharp;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ShellContentRenderer : IShellItemRenderer
+ {
+ public ShellContentRenderer(ShellContent content)
+ {
+ ShellContent = content;
+ NativeView = GetNativeView(content);
+ }
+
+ public ShellContent ShellContent { get; protected set; }
+
+ public BaseShellItem Item => ShellContent;
+
+ public EvasObject NativeView { get; protected set; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ NativeView?.Unrealize();
+ }
+ }
+
+ static EvasObject GetNativeView(ShellContent content)
+ {
+ var page = (content as IShellContentController).GetOrCreateContent();
+ return Platform.GetOrCreateRenderer(page).NativeView;
+ }
+ }
+}
--- /dev/null
+using ElmSharp;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Xamarin.Forms;
+using XForms = Xamarin.Forms.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ShellItemRenderer : IShellItemRenderer
+ {
+ Box _mainLayout;
+ EvasObject _currentItem;
+ Dictionary<BaseShellItem, IShellItemRenderer> _rendererCache = new Dictionary<BaseShellItem, IShellItemRenderer>();
+
+ public ShellItemRenderer(ShellItem item)
+ {
+ ShellItem = item;
+ ShellItem.PropertyChanged += OnItemPropertyChanged;
+ InitializeComponent();
+ UpdateCurrentItem();
+ }
+
+ public ShellItem ShellItem { get; protected set; }
+
+ public BaseShellItem Item => ShellItem;
+
+ public EvasObject NativeView => _mainLayout;
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ ResetCurrentItem();
+ ShellItem.PropertyChanged -= OnItemPropertyChanged;
+ }
+ }
+
+ void InitializeComponent()
+ {
+ _mainLayout = new Box(XForms.NativeParent);
+ _mainLayout.SetLayoutCallback(OnLayout);
+ }
+
+ void UpdateCurrentItem()
+ {
+ ResetCurrentItem();
+ var currentItem = ShellItem.CurrentItem;
+ if (currentItem != null)
+ {
+ if (!_rendererCache.TryGetValue(currentItem, out IShellItemRenderer renderer))
+ {
+ renderer = ShellRendererFactory.Default.CreateShellNavigationRenderer(currentItem);
+ _rendererCache[currentItem] = renderer;
+ }
+ SetCurrentItem(renderer.NativeView);
+ }
+ }
+
+ void SetCurrentItem(EvasObject item)
+ {
+ _currentItem = item;
+ _currentItem.Show();
+ _mainLayout.PackEnd(_currentItem);
+ }
+
+ void ResetCurrentItem()
+ {
+ if (_currentItem != null)
+ {
+ _mainLayout.UnPack(_currentItem);
+ _currentItem.Hide();
+ _currentItem = null;
+ }
+ }
+
+ void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(ShellItem.CurrentItem))
+ {
+ UpdateCurrentItem();
+ }
+ }
+
+ void OnLayout()
+ {
+ if (_currentItem != null)
+ {
+ _currentItem.Geometry = _mainLayout.Geometry;
+ }
+ }
+ }
+}
--- /dev/null
+using ElmSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using XForms = Xamarin.Forms.Forms;
+using XShell = Xamarin.Forms.Shell;
+using Tizen.Wearable.CircularUI.Forms;
+
+[assembly: ExportRenderer(typeof(CircularShell), typeof(Tizen.Wearable.CircularUI.Forms.Renderer.ShellRenderer))]
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ShellRenderer : VisualElementRenderer<XShell>
+ {
+ NavigationDrawer _drawer;
+ NavigationView _navigationView;
+
+ Dictionary<BaseShellItem, IShellItemRenderer> _rendererCache = new Dictionary<BaseShellItem, IShellItemRenderer>();
+
+ public ShellRenderer()
+ {
+ RegisterPropertyHandler(XShell.CurrentItemProperty, UpdateCurrentItem);
+ RegisterPropertyHandler(XShell.FlyoutIsPresentedProperty, UpdateFlyoutIsPresented);
+ RegisterPropertyHandler(XShell.FlyoutBehaviorProperty, UpdateFlyoutBehavior);
+ RegisterPropertyHandler(XShell.FlyoutIconProperty, UpdateFlyoutIcon);
+ RegisterPropertyHandler(XShell.FlyoutBackgroundColorProperty, UpdateFlyoutBackgroundColor);
+ RegisterPropertyHandler(CircularShell.FlyoutIconBackgroundColorProperty, UpdateFlyoutIconBackgroundColor);
+ RegisterPropertyHandler(CircularShell.FlyoutForegroundColorProperty, UpdateFlyoutForegroundColor);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<XShell> e)
+ {
+ InitializeComponent();
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementReady()
+ {
+ base.OnElementReady();
+ UpdateFlyoutMenu();
+ (Element as IShellController).StructureChanged += OnNavigationStructureChanged;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (var renderer in _rendererCache.Values)
+ {
+ renderer.Dispose();
+ }
+ (Element as IShellController).StructureChanged -= OnNavigationStructureChanged;
+ }
+ base.Dispose(disposing);
+ }
+
+ void InitializeComponent()
+ {
+ if (_drawer == null)
+ {
+ _drawer = new NavigationDrawer(XForms.NativeParent);
+ _drawer.IsOpen = Element.FlyoutIsPresented;
+ _drawer.Toggled += OnNavigationDrawerToggled;
+ SetNativeView(_drawer);
+ }
+ }
+
+ void OnNavigationStructureChanged(object sender, EventArgs e)
+ {
+ UpdateFlyoutMenu();
+ }
+
+ void UpdateFlyoutMenu()
+ {
+ if (Element.FlyoutBehavior == FlyoutBehavior.Disabled)
+ return;
+
+ var flyoutItems = (Element as IShellController).GenerateFlyoutGrouping();
+ int itemCount = 0;
+ foreach (var item in flyoutItems)
+ {
+ itemCount += item.Count;
+ }
+
+ if (itemCount > 1)
+ {
+ InitializeNavigationDrawer();
+ _navigationView.Build(flyoutItems);
+ }
+ else
+ {
+ DeinitializeNavigationView();
+ }
+ }
+
+ void InitializeNavigationDrawer()
+ {
+ if (_navigationView != null)
+ {
+ return;
+ }
+
+ _navigationView = new NavigationView(XForms.NativeParent)
+ {
+ AlignmentX = -1,
+ AlignmentY = -1,
+ WeightX = 1,
+ WeightY = 1,
+ };
+ _navigationView.Show();
+ _navigationView.ItemSelected += OnMenuItemSelected;
+
+ _drawer.SetDrawerContent(_navigationView);
+ }
+
+ void OnNavigationDrawerToggled(object sender, EventArgs e)
+ {
+ if (_drawer.IsOpen)
+ {
+ _navigationView.Activate();
+ }
+ else
+ {
+ _navigationView.Deactivate();
+
+ var stack = (Element.CurrentItem.CurrentItem as ShellSection)?.Stack;
+ var currentPage = stack?.LastOrDefault<Page>();
+
+ if (currentPage == null)
+ {
+ currentPage = (Element.CurrentItem.CurrentItem.CurrentItem as IShellContentController)?.Page;
+ }
+
+ if (currentPage != null)
+ {
+ var renderer = Platform.GetOrCreateRenderer(currentPage);
+ (renderer as CirclePageRenderer)?.UpdateRotaryFocusObject();
+ }
+ }
+
+ Element.SetValueFromRenderer(XShell.FlyoutIsPresentedProperty, _drawer.IsOpen);
+ }
+
+ void DeinitializeNavigationView()
+ {
+ if (_navigationView == null)
+ return;
+ _drawer.SetDrawerContent(null);
+ _navigationView.Unrealize();
+ _navigationView = null;
+ }
+
+ void OnMenuItemSelected(object sender, SelectedItemChangedEventArgs e)
+ {
+ ((IShellController)Element).OnFlyoutItemSelected(e.SelectedItem as Element);
+ }
+
+ void UpdateCurrentItem()
+ {
+ ResetCurrentItem();
+ if (Element.CurrentItem != null)
+ {
+ if (!_rendererCache.TryGetValue(Element.CurrentItem, out IShellItemRenderer renderer))
+ {
+ renderer = ShellRendererFactory.Default.CreateItemRenderer(Element.CurrentItem);
+ _rendererCache[Element.CurrentItem] = renderer;
+ }
+ SetCurrentItem(renderer.NativeView);
+ }
+ }
+
+ void UpdateFlyoutBehavior(bool init)
+ {
+ if (init)
+ return;
+
+ if (Element.FlyoutBehavior == FlyoutBehavior.Disabled)
+ {
+ DeinitializeNavigationView();
+ }
+ else if (Element.FlyoutBehavior == FlyoutBehavior.Flyout)
+ {
+ UpdateFlyoutMenu();
+ }
+ else if (Element.FlyoutBehavior == FlyoutBehavior.Locked)
+ {
+ // Locked behavior is not supported on circularshell
+ }
+ }
+
+ void UpdateFlyoutIcon(bool init)
+ {
+ if (init && Element.FlyoutIcon == null)
+ return;
+
+ _drawer.UpdateDrawerIcon(Element.FlyoutIcon);
+ }
+
+ void UpdateFlyoutBackgroundColor(bool init)
+ {
+ if (init && Element.FlyoutBackgroundColor.IsDefault)
+ return;
+
+ if (_navigationView != null)
+ {
+ _navigationView.BackgroundColor = Element.FlyoutBackgroundColor.ToNative();
+ }
+ }
+
+ void UpdateFlyoutForegroundColor(bool init)
+ {
+ if (init && CircularShell.GetFlyoutForegroundColor(Element).IsDefault)
+ return;
+
+ if (_navigationView != null)
+ {
+ _navigationView.ForegroundColor = CircularShell.GetFlyoutForegroundColor(Element).ToNative();
+ }
+ }
+
+ void UpdateFlyoutIconBackgroundColor()
+ {
+ _drawer.HandlerBackgroundColor = CircularShell.GetFlyoutIconBackgroundColor(Element).ToNative();
+ }
+
+ void UpdateFlyoutIsPresented()
+ {
+ _drawer.IsOpen = Element.FlyoutIsPresented;
+ }
+
+ void SetCurrentItem(EvasObject item)
+ {
+ _drawer.SetMainContent(item);
+ }
+
+ void ResetCurrentItem()
+ {
+ _drawer.SetMainContent(null);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Xamarin.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ShellRendererFactory
+ {
+ static ShellRendererFactory _instance;
+ public static ShellRendererFactory Default
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new ShellRendererFactory();
+ }
+ return _instance;
+ }
+ set
+ {
+ _instance = value;
+ }
+
+ }
+
+ public virtual IShellItemRenderer CreateItemRenderer(ShellItem item)
+ {
+ if (item.Items.Count == 1)
+ {
+ return CreateShellNavigationRenderer(item.CurrentItem);
+ }
+ return new ShellItemRenderer(item);
+ }
+
+ public virtual IShellItemRenderer CreateShellNavigationRenderer(ShellSection item)
+ {
+ return new ShellSectionNavigationRenderer(item);
+ }
+
+ public virtual IShellItemRenderer CreateItemRenderer(ShellSection item)
+ {
+ if (item.Items.Count == 1)
+ {
+ return CreateItemRenderer(item.CurrentItem);
+ }
+ return new ShellSectionItemsRenderer(item);
+ }
+
+ public virtual IShellItemRenderer CreateItemRenderer(ShellContent item)
+ {
+ return new ShellContentRenderer(item);
+ }
+ }
+}
--- /dev/null
+using ElmSharp;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Xamarin.Forms;
+using XForms = Xamarin.Forms.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ public class ShellSectionItemsRenderer : IShellItemRenderer
+ {
+ const int ItemMaxCount = 20;
+ const int OddMiddleItem = 10;
+ const int EvenMiddleItem = 11;
+
+ Box _mainLayout;
+ Index _indexIndicator;
+ Scroller _scroller;
+ Box _innerContainer;
+ List<ItemHolder> _items = new List<ItemHolder>();
+
+ int _currentIndex = -1;
+ Rect _lastLayoutBound;
+ int _updateByCode;
+
+
+ public ShellSectionItemsRenderer(ShellSection shellSection)
+ {
+ ShellSection = shellSection;
+ ShellSection.PropertyChanged += OnSectionPropertyChanged;
+ (ShellSection.Items as INotifyCollectionChanged).CollectionChanged += OnItemsChanged;
+ InitializeComponent();
+ UpdateItems();
+ }
+
+ public ShellSection ShellSection { get; protected set; }
+
+ public BaseShellItem Item => ShellSection;
+
+ public EvasObject NativeView => _mainLayout;
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _mainLayout?.Unrealize();
+ (ShellSection.Items as INotifyCollectionChanged).CollectionChanged -= OnItemsChanged;
+ ShellSection.PropertyChanged -= OnSectionPropertyChanged;
+ }
+ }
+
+ void InitializeComponent()
+ {
+ _mainLayout = new Box(XForms.NativeParent)
+ {
+ AlignmentX = -1,
+ AlignmentY = -1,
+ WeightX = 1,
+ WeightY = 1,
+ };
+ _mainLayout.Show();
+ _mainLayout.SetLayoutCallback(OnLayout);
+
+ _indexIndicator = new Index(_mainLayout)
+ {
+ IsHorizontal = true,
+ AutoHide = false,
+ Style = IndexStyle.Circle,
+ };
+ _indexIndicator.Show();
+
+ _scroller = new Scroller(_mainLayout);
+ _scroller.PageScrolled += OnPageScrolled;
+ _scroller.DragStart += OnDragStarted;
+
+ // Disables the visibility of the scrollbar in both directions:
+ _scroller.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible;
+ _scroller.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible;
+ // Sets the limit of scroll to one page maximum:
+ _scroller.HorizontalPageScrollLimit = 1;
+ _scroller.SetPageSize(1.0, 1.0);
+ _scroller.SetAlignment(-1, -1);
+ _scroller.SetWeight(1.0, 1.0);
+ _scroller.Show();
+
+ _innerContainer = new Box(_mainLayout);
+ _innerContainer.SetLayoutCallback(OnInnerLayoutUpdate);
+ _innerContainer.SetAlignment(-1, -1);
+ _innerContainer.SetWeight(1.0, 1.0);
+ _innerContainer.Show();
+ _scroller.SetContent(_innerContainer);
+
+ _mainLayout.PackEnd(_indexIndicator);
+ _mainLayout.PackEnd(_scroller);
+ _indexIndicator.StackAbove(_scroller);
+ }
+
+ void OnDragStarted(object sender, EventArgs e)
+ {
+ if (_currentIndex - 1 >= 0 && !_items[_currentIndex - 1].IsRealized)
+ {
+ RealizeItem(_items[_currentIndex - 1]);
+ }
+ if (_currentIndex + 1 < _items.Count && !_items[_currentIndex + 1].IsRealized)
+ {
+ RealizeItem(_items[_currentIndex + 1]);
+ }
+ }
+
+ void UpdateItems()
+ {
+ _items.Clear();
+ _indexIndicator.Clear();
+ _innerContainer.UnPackAll();
+ _lastLayoutBound = default(Rect);
+
+ foreach (var item in ShellSection.Items)
+ {
+ var indexItem = _indexIndicator.Append(null);
+ indexItem.Style = GetItemStyle(ShellSection.Items.Count, _items.Count);
+ _items.Add(new ItemHolder
+ {
+ IsRealized = false,
+ IndexItem = indexItem,
+ Item = item
+ });
+ }
+ _indexIndicator.Update(0);
+ UpdateCurrentPage(ShellSection.Items.IndexOf(ShellSection.CurrentItem));
+ }
+
+ void RealizeItem(ItemHolder item)
+ {
+ var renderer = ShellRendererFactory.Default.CreateItemRenderer(item.Item);
+ renderer.NativeView.Show();
+ item.NativeView = renderer.NativeView;
+ item.IsRealized = true;
+ _innerContainer.PackEnd(item.NativeView);
+ item.NativeView.StackBelow(_indexIndicator);
+ item.NativeView.Geometry = item.Bound;
+ }
+
+ void UpdateCurrentPage(int index)
+ {
+ if (_currentIndex == index)
+ return;
+
+ _currentIndex = index;
+ UpdateCurrentIndexIndicator();
+ if (!_items[index].IsRealized)
+ {
+ RealizeItem(_items[index]);
+ }
+ UpdateFocusPolicy();
+ }
+
+ void UpdateFocusPolicy()
+ {
+ foreach (var item in _items)
+ {
+ if (item.IsRealized)
+ {
+ if (item.NativeView is ElmSharp.Widget widget)
+ {
+ widget.AllowTreeFocus = (_items[_currentIndex] == item);
+ }
+ }
+ }
+ }
+
+ void UpdateCurrentIndexIndicator()
+ {
+ if (_currentIndex >= 0 && _currentIndex < _items.Count)
+ {
+ _items[_currentIndex].IndexItem.Select(true);
+ }
+ }
+ void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateItems();
+ }
+
+ void OnPageScrolled(object sender, EventArgs e)
+ {
+ if (_updateByCode > 0)
+ {
+ return;
+ }
+
+ if (_currentIndex < 0 || ShellSection.Items.Count <= _currentIndex)
+ {
+ return;
+ }
+
+ UpdateCurrentPage(_scroller.HorizontalPageIndex);
+ var currentItem = ShellSection.Items[_currentIndex];
+ ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, currentItem);
+ }
+
+ void OnSectionPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(ShellSection.CurrentItem))
+ {
+ var newIndex = ShellSection.Items.IndexOf(ShellSection.CurrentItem);
+ if (_currentIndex != newIndex)
+ {
+ UpdateCurrentPage(newIndex);
+ _updateByCode++;
+ _scroller.ScrollTo(newIndex, 0, false);
+ _updateByCode--;
+ }
+ }
+ }
+
+ void OnLayout()
+ {
+ _indexIndicator.Geometry = _mainLayout.Geometry;
+ _scroller.Geometry = _mainLayout.Geometry;
+ }
+
+ void OnInnerLayoutUpdate()
+ {
+ if (_lastLayoutBound == _innerContainer.Geometry)
+ {
+ return;
+ }
+ _lastLayoutBound = _innerContainer.Geometry;
+
+ var layoutBound = _innerContainer.Geometry.Size;
+ int baseX = _innerContainer.Geometry.X;
+
+ Rect bound = _scroller.Geometry;
+ int index = 0;
+ foreach (var item in _items)
+ {
+ bound.X = baseX + index * bound.Width;
+ item.Bound = bound;
+ if (item.IsRealized)
+ {
+ item.NativeView.Geometry = bound;
+ }
+ index++;
+ }
+ _innerContainer.MinimumWidth = _items.Count * bound.Width;
+
+ if (_items.Count > _currentIndex && _currentIndex >= 0)
+ {
+ _updateByCode++;
+ _scroller.ScrollTo(_currentIndex, 0, false);
+ _updateByCode--;
+ }
+ }
+
+ static string GetItemStyle(int itemCount, int offset)
+ {
+ string returnValue = string.Empty;
+ int startItem;
+ int styleNumber;
+
+ if (itemCount % 2 == 0) //Item count is even.
+ {
+ startItem = EvenMiddleItem - itemCount / 2;
+ styleNumber = startItem + offset;
+ returnValue = "item/even_" + styleNumber;
+ }
+ else //Item count is odd.
+ {
+ startItem = OddMiddleItem - itemCount / 2;
+ styleNumber = startItem + offset;
+ returnValue = "item/odd_" + styleNumber;
+ }
+ return returnValue;
+ }
+
+ class ItemHolder
+ {
+ public bool IsRealized { get; set; }
+ public Rect Bound { get; set; }
+ public EvasObject NativeView { get; set; }
+ public IndexItem IndexItem { get; set; }
+ public ShellContent Item { get; set; }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using ElmSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.Tizen;
+using XForms = Xamarin.Forms.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms.Renderer
+{
+ class SimpleViewStack : Box
+ {
+ EvasObject _lastTop;
+
+ public SimpleViewStack(EvasObject parent) : base(parent)
+ {
+ InternalStack = new List<EvasObject>();
+ SetLayoutCallback(OnLayout);
+ }
+
+ List<EvasObject> InternalStack { get; set; }
+
+ public IReadOnlyList<EvasObject> Stack => InternalStack;
+
+ public void Push(EvasObject view)
+ {
+ InternalStack.Add(view);
+ PackEnd(view);
+ UpdateTopView();
+ }
+
+ public void Pop()
+ {
+ if (_lastTop != null)
+ {
+ var tobeRemoved = _lastTop;
+ InternalStack.Remove(tobeRemoved);
+ UnPack(tobeRemoved);
+ UpdateTopView();
+
+ // if Pop was called by removed page,
+ // Unrealize cause deletation of NativeCallback, it could be a cause of crash
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ tobeRemoved.Unrealize();
+ });
+ }
+ }
+
+ public void PopToRoot()
+ {
+ while (InternalStack.Count > 1)
+ {
+ Pop();
+ }
+ }
+
+ public void Insert(EvasObject before, EvasObject view)
+ {
+ view.Hide();
+ var idx = InternalStack.IndexOf(before);
+ InternalStack.Insert(idx, view);
+ PackEnd(view);
+ UpdateTopView();
+ }
+
+ public void Remove(EvasObject view)
+ {
+ InternalStack.Remove(view);
+ UnPack(view);
+ UpdateTopView();
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ view?.Unrealize();
+ });
+ }
+
+ void UpdateTopView()
+ {
+ if (_lastTop != InternalStack.LastOrDefault())
+ {
+ _lastTop?.Hide();
+ _lastTop = InternalStack.LastOrDefault();
+ _lastTop.Show();
+ }
+ }
+
+ void OnLayout()
+ {
+ foreach (var view in Stack)
+ {
+ view.Geometry = Geometry;
+ }
+ }
+
+ }
+
+ public class ShellSectionNavigationRenderer : IShellItemRenderer
+ {
+ SimpleViewStack _viewStack;
+ IShellItemRenderer _rootPageRenderer;
+
+ public ShellSectionNavigationRenderer(ShellSection item)
+ {
+ ShellSection = item;
+ (ShellSection as IShellSectionController).NavigationRequested += OnNavigationRequested;
+ InitializeComponent();
+ }
+
+ public ShellSection ShellSection { get; protected set; }
+
+ public BaseShellItem Item => ShellSection;
+
+ public EvasObject NativeView => _viewStack;
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _rootPageRenderer?.Dispose();
+ _viewStack?.Unrealize();
+ (ShellSection as IShellSectionController).NavigationRequested -= OnNavigationRequested;
+ }
+ }
+
+ void InitializeComponent()
+ {
+ _viewStack = new SimpleViewStack(XForms.NativeParent);
+ _viewStack.Show();
+
+ _rootPageRenderer = ShellRendererFactory.Default.CreateItemRenderer(ShellSection);
+ _viewStack.Push(_rootPageRenderer.NativeView);
+ }
+
+ void OnInsertRequest(NavigationRequestedEventArgs request)
+ {
+ var before = Platform.GetRenderer(request.BeforePage)?.NativeView ?? null;
+ if (before == null)
+ {
+ request.Task = Task.FromException<bool>(new ArgumentException("Can't found page on stack", nameof(request.BeforePage)));
+ return;
+ }
+ var renderer = Platform.GetOrCreateRenderer(request.Page);
+ _viewStack.Insert(before, renderer.NativeView);
+ request.Task = Task.FromResult(true);
+ }
+
+ void OnPushRequest(NavigationRequestedEventArgs request)
+ {
+ var renderer = Platform.GetOrCreateRenderer(request.Page);
+ _viewStack.Push(renderer.NativeView);
+ request.Task = Task.FromResult(true);
+ }
+
+ void OnPopRequest(NavigationRequestedEventArgs request)
+ {
+ _viewStack.Pop();
+ request.Task = Task.FromResult(true);
+ }
+
+ void OnPopToRootRequest(NavigationRequestedEventArgs request)
+ {
+ _viewStack.PopToRoot();
+ request.Task = Task.FromResult(true);
+ }
+
+ void OnRemoveRequest(NavigationRequestedEventArgs request)
+ {
+ var renderer = Platform.GetRenderer(request.Page);
+ if (renderer == null)
+ {
+ request.Task = Task.FromException<bool>(new ArgumentException("Can't found page on stack", nameof(request.Page)));
+ return;
+ }
+ _viewStack.Remove(renderer.NativeView);
+ request.Task = Task.FromResult(true);
+ }
+
+ void OnNavigationRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ switch (e.RequestType)
+ {
+ case NavigationRequestType.Insert:
+ OnInsertRequest(e);
+ break;
+ case NavigationRequestType.Push:
+ OnPushRequest(e);
+ break;
+ case NavigationRequestType.Pop:
+ OnPopRequest(e);
+ break;
+ case NavigationRequestType.PopToRoot:
+ OnPopToRootRequest(e);
+ break;
+ case NavigationRequestType.Remove:
+ OnRemoveRequest(e);
+ break;
+ }
+ }
+ }
+}
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Tizen;
+using XForms = Xamarin.Forms.Forms;
[assembly: ExportEffect(typeof(Tizen.Wearable.CircularUI.Forms.Renderer.TizenCircleSurfaceEffect), "CircleSurfaceEffect")]
namespace Tizen.Wearable.CircularUI.Forms.Renderer
{
var rect = Control.Geometry;
- _surfaceLayout = new ElmSharp.Layout(Container);
+ _surfaceLayout = new ElmSharp.Layout(XForms.NativeParent);
_surfaceLayout.Show();
_surface = new ElmSharp.Wearable.CircleSurface(_surfaceLayout);
_surfaceLayout.Geometry = rect;
void UpdateTitle()
{
- _layout.SetPartText("elm.text.title", _title);
+ string title = _title?.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace(Environment.NewLine, "<br>");
+ _layout.SetPartText("elm.text.title", title);
}
void UpdateText()
{
- _layout.SetPartText("elm.text", _text);
+ string text = _text?.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace(Environment.NewLine, "<br>");
+ _layout.SetPartText("elm.text", text);
}
public void Show()
* limitations under the License.
*/
-using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.ComponentModel;
using Xamarin.Forms;
namespace Tizen.Wearable.CircularUI.Forms
/// <since_tizen> 4 </since_tizen>
public IList<ICircleSurfaceItem> CircleSurfaceItems { get; }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Appeared { get; set; }
+
/// <summary>
/// Gets or sets ActionButton that presents a menu item and associates it with a command
/// </summary>
}
}
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ Appeared = true;
+ }
+
+ protected override void OnDisappearing()
+ {
+ base.OnDisappearing();
+ Appeared = false;
+ }
+
void OnSurfaceItemsChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.Action != NotifyCollectionChangedAction.Add) return;
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using Xamarin.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms
+{
+ public class CircleSurfaceView : View
+ {
+ public IList<ICircleSurfaceItem> CircleSurfaceItems { get; internal set; }
+
+ public CircleSurfaceView()
+ {
+ var circleSurfaceItems = new ObservableCollection<ICircleSurfaceItem>();
+ circleSurfaceItems.CollectionChanged += OnCircleObjectItemsCollectionChanged;
+ CircleSurfaceItems = circleSurfaceItems;
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ foreach (CircleSurfaceItem item in CircleSurfaceItems)
+ {
+ SetInheritedBindingContext(item, BindingContext);
+ }
+ }
+
+ void OnCircleObjectItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (args.Action != NotifyCollectionChangedAction.Add)
+ return;
+ foreach (Element item in args.NewItems)
+ item.Parent = this;
+ }
+ }
+}
--- /dev/null
+using Xamarin.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms
+{
+ public class CircularShell : Shell
+ {
+ public static readonly BindableProperty FlyoutIconBackgroundColorProperty = BindableProperty.CreateAttached("FlyoutIconBackgroundColor", typeof(Color), typeof(Shell), Color.Default);
+ public static readonly BindableProperty FlyoutForegroundColorProperty = BindableProperty.CreateAttached("FlyoutForegroundColor", typeof(Color), typeof(Shell), Color.Default);
+
+ public static Color GetFlyoutIconBackgroundColor(BindableObject element)
+ {
+ return (Color)element.GetValue(FlyoutIconBackgroundColorProperty);
+ }
+
+ public static void SetFlyoutIconBackgroundColor(BindableObject element, Color color)
+ {
+ element.SetValue(FlyoutIconBackgroundColorProperty, color);
+ }
+
+ public static Color GetFlyoutForegroundColor(BindableObject element)
+ {
+ return (Color)element.GetValue(FlyoutForegroundColorProperty);
+ }
+
+ public static void SetFlyoutForegroundColor(BindableObject element, Color color)
+ {
+ element.SetValue(FlyoutForegroundColorProperty, color);
+ }
+
+ public Color FlyoutIconBackgroundColor
+ {
+ get => (Color)GetValue(FlyoutIconBackgroundColorProperty);
+ set => SetValue(FlyoutIconBackgroundColorProperty, value);
+ }
+
+ public Color FlyoutForegroundColor
+ {
+ get => (Color)GetValue(FlyoutForegroundColorProperty);
+ set => SetValue(FlyoutForegroundColorProperty, value);
+ }
+
+ protected override bool OnBackButtonPressed()
+ {
+ if (FlyoutIsPresented)
+ {
+ FlyoutIsPresented = false;
+ return true;
+ }
+ return base.OnBackButtonPressed();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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.Windows.Input;
+using Xamarin.Forms;
+
+namespace Tizen.Wearable.CircularUI.Forms
+{
+ /// <summary>
+ /// The ContentButton is a Button, which allows you to customize the View to be displayed.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public class ContentButton : ContentView, IButtonController
+ {
+ /// <summary>
+ /// BindableProperty. Identifies the Command bindable property.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContentButton), null,
+ propertyChanging: OnCommandChanging, propertyChanged: OnCommandChanged);
+
+ /// <summary>
+ /// BindableProperty. Identifies the CommandParameter bindable property.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton), null,
+ propertyChanged: (bindable, oldvalue, newvalue) => CommandCanExcuteChanged(bindable, EventArgs.Empty));
+
+ /// <summary>
+ /// Gets or sets command that is executed when the button is clicked.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public ICommand Command
+ {
+ get => (ICommand)GetValue(CommandProperty);
+ set => SetValue(CommandProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets command paramter that is executed when the button is clicked.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public object CommandParameter
+ {
+ get => GetValue(CommandParameterProperty);
+ set => SetValue(CommandParameterProperty, value);
+ }
+
+ /// <summary>
+ /// Occurs when the button is clicked.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public event EventHandler Clicked;
+
+ /// <summary>
+ /// Occurs when the button is pressed.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public event EventHandler Pressed;
+
+ /// <summary>
+ /// Occurs when the button is released.
+ /// </summary>
+ /// <since_tizen> 4 </since_tizen>
+ public event EventHandler Released;
+
+ bool IsEnabledCore
+ {
+ set => SetValueCore(IsEnabledProperty, value);
+ }
+
+ /// <summary>
+ /// For internal use.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendClicked()
+ {
+ if (IsEnabled)
+ {
+ Command?.Execute(CommandParameter);
+ Clicked?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ /// <summary>
+ /// For internal use.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendPressed()
+ {
+ if (IsEnabled)
+ {
+ Pressed?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ /// <summary>
+ /// For internal use.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendReleased()
+ {
+ if (IsEnabled)
+ {
+ Released?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ View content = Content;
+ if (content != null)
+ {
+ SetInheritedBindingContext(content, BindingContext);
+ }
+ }
+
+ static void OnCommandChanged(BindableObject bindable, object oldCommand, object newCommand)
+ {
+ ContentButton button = (ContentButton)bindable;
+ if (newCommand is ICommand command)
+ {
+ command.CanExecuteChanged += button.OnCommandCanExecuteChanged;
+ }
+ CommandChanged(button);
+ }
+
+ static void CommandChanged(ContentButton button)
+ {
+ if(button.Command != null)
+ {
+ CommandCanExcuteChanged(button, EventArgs.Empty);
+ }
+ else
+ {
+ button.IsEnabledCore = true;
+ }
+ }
+
+ static void OnCommandChanging(BindableObject bindable, object oldCommand, object newCommand)
+ {
+ ContentButton button = (ContentButton)bindable;
+ if (oldCommand != null)
+ {
+ (oldCommand as ICommand).CanExecuteChanged -= button.OnCommandCanExecuteChanged;
+ }
+ }
+
+ static void CommandCanExcuteChanged(object sender, EventArgs e)
+ {
+ var button = (ContentButton)sender;
+ if (button.Command != null)
+ {
+ button.IsEnabledCore = button.Command.CanExecute(button.CommandParameter);
+ }
+ }
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs e)
+ {
+ ContentButton button = (ContentButton)sender;
+ if (button.Command != null)
+ {
+ button.IsEnabledCore = button.Command.CanExecute(button.CommandParameter);
+ }
+ }
+ }
+}
<EmbeddedResource Include="Resources\img_button_pause.png" />
<EmbeddedResource Include="Resources\img_button_play.png" />
<EmbeddedResource Include="Resources\circle.png" />
+ <EmbeddedResource Include="Resources\wc_visual_cue.png" />
</ItemGroup>
</Project>
\ No newline at end of file
{
public class EmbeddingControls : ContentView
{
- static readonly string PlayImagePath = "XSF.Resource.img_button_play.png";
- static readonly string PauseImagePath = "XSF.Resource.img_button_pause.png";
+ static readonly string PlayImagePath = "XSF.Resources.img_button_play.png";
+ static readonly string PauseImagePath = "XSF.Resources.img_button_pause.png";
public ImageButton PlayImage { get; private set; }
public ImageButton PauseImage { get; private set; }
{
public static readonly int IconSize = 48;
static readonly Color DefaultColor = Color.FromHex("#6200EE");
- static readonly string IconPath = "XSF.Resource.refresh_48dp.png";
+ static readonly string IconPath = "XSF.Resources.refresh_48dp.png";
bool _isPlaying;
Image _icon;
bool _disposed = false;
EColor _backgroudColor = ShellRenderer.DefaultBackgroundColor.ToNative();
// The source of icon resources is https://materialdesignicons.com/
- const string _dotsIcon = "XSF.Resource.dots_horizontal.png";
+ const string _dotsIcon = "XSF.Resources.dots_horizontal.png";
public ShellItemRenderer(IFlyoutController flyoutController, ShellItem item)
{
EColor _foregroudColor = ShellRenderer.DefaultForegroundColor.ToNative();
// The source of icon resources is https://materialdesignicons.com/
- const string _menuIcon = "XSF.Resource.menu.png";
- const string _backIcon = "XSF.Resource.arrow_left.png";
+ const string _menuIcon = "XSF.Resources.menu.png";
+ const string _backIcon = "XSF.Resources.arrow_left.png";
bool _hasBackButton = false;