--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+
+namespace ScalableUI
+{
+ /// <summary>
+ /// MarginalView.
+ /// </summary>
+ public class MarginalView : View
+ {
+ private const float DebugOpacity = 0.4f;
+
+ private readonly View marginalContentView;
+ private readonly View sideLeft;
+ private readonly View sideRight;
+
+ private View debugLeftMargin;
+ private View debugMiddleContent;
+ private View debugRightMargin;
+
+ private int marginalWidth;
+
+ private float maxMargin = 0.25f;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MarginalView"/> class.
+ /// </summary>
+ public MarginalView()
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent;
+ ClippingMode = ClippingModeType.ClipChildren;
+
+ View container = new View()
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ };
+ base.Add(container);
+
+ marginalContentView = new View()
+ {
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ };
+ sideLeft = new View()
+ {
+ SizeWidth = 10,
+ BackgroundColor = Color.Red,
+ };
+ sideRight = new View()
+ {
+ SizeWidth = 10,
+ BackgroundColor = Color.Blue,
+ };
+ container.Add(sideLeft);
+ container.Add(marginalContentView);
+ container.Add(sideRight);
+
+ Relayout += OnRelayout;
+
+ InitDebug();
+ }
+
+ /// <summary>
+ /// Gets or sets MaxMargin.
+ /// </summary>
+ public float MaxMargin
+ {
+ get => maxMargin;
+ set
+ {
+ maxMargin = value;
+ RelayoutMarginalContentView();
+ }
+ }
+
+ /// <summary>
+ /// Gets MarginalWidth.
+ /// </summary>
+ public int MarginalWidth => marginalContentView.Size2D.Width;
+
+ /// <summary>
+ /// Sets a value indicating whether set.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShowMarginalDebugColors
+ {
+ set
+ {
+ if (value)
+ {
+ debugLeftMargin.Show();
+ debugMiddleContent.Show();
+ debugRightMargin.Show();
+ }
+ else
+ {
+ debugLeftMargin.Hide();
+ debugMiddleContent.Hide();
+ debugRightMargin.Hide();
+ }
+ }
+ }
+
+ private static Size MinBreakpointSize { get; } = new Size(1024, 720);
+
+ private static Size MaxBreakpointSize { get; } = new Size(4096, 2160);
+
+ /// <inheritdoc />
+ public override void Add(View child)
+ {
+ marginalContentView.Add(child);
+ }
+
+ /// <inheritdoc />
+ public override void Remove(View child)
+ {
+ marginalContentView.Remove(child);
+ }
+
+ private static List<float> GetMarginalBreakpoints(float min, float max, float margin)
+ {
+ var breakpoints = new List<float>();
+ float lastBreakpoint = min;
+
+ while (lastBreakpoint < max)
+ {
+ breakpoints.Add(lastBreakpoint);
+ lastBreakpoint = lastBreakpoint * (1 + margin);
+ }
+
+ return breakpoints;
+ }
+
+ private static float GetMarginalWidth(float margin)
+ {
+ bool isLandscape = Window.Instance.WindowSize.Width > Window.Instance.WindowSize.Height;
+ float min = isLandscape ? MinBreakpointSize.Width : MinBreakpointSize.Height;
+ float max = isLandscape ? MaxBreakpointSize.Width : MaxBreakpointSize.Height;
+ List<float> breakpoints = GetMarginalBreakpoints(min, max, margin);
+
+ float limit = Window.Instance.WindowSize.Width;
+ float breakpoint = breakpoints.Where(x => x <= limit).Last();
+
+ LogToDebug(breakpoints, limit, breakpoint, margin);
+
+ return breakpoint;
+ }
+
+ private static void LogToDebug(IEnumerable<float> breakpoints, float limit, float breakpoint, float margin)
+ {
+ IEnumerable<string> breakpoints__ = breakpoints.Select(x => Convert.ToInt32(x).ToString());
+ double effectiveMarginPixels = limit - breakpoint;
+ double effectiveMarginPercent = Math.Round(effectiveMarginPixels / limit, 2);
+
+ // Logger.Debug($"Window size {Window.Instance.WindowSize.Width} x {Window.Instance.WindowSize.Height} px");
+ // Logger.Debug($"Horizontal breakpoints [{string.Join(",", breakpoints__)}] px, Selected {breakpoint} px is below {Window.Instance.WindowSize.Width} px");
+ // Logger.Debug($"MaxMargin = {margin}, effective margin = {effectiveMarginPercent} ({effectiveMarginPixels} px)");
+ }
+
+ private void OnRelayout(object sender, EventArgs e)
+ {
+ RelayoutMarginalContentView();
+ }
+
+ private void RelayoutMarginalContentView()
+ {
+ marginalWidth = (int)GetMarginalWidth(MaxMargin);
+ int margin = (Window.Instance.WindowSize.Width - marginalWidth) / 2;
+
+ sideLeft.SizeWidth = margin;
+ sideRight.SizeWidth = margin;
+
+ RelayoutDebug(margin);
+ }
+
+ private void InitDebug()
+ {
+ View GetDebugContainer(Color color, string text)
+ {
+ View debugText = new TextLabel(text)
+ {
+ PointSize = 3,
+ TextColor = Color.Black,
+ PositionUsesPivotPoint = true,
+ PivotPoint = Position.PivotPointCenter,
+ ParentOrigin = Position.ParentOriginCenter,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ MultiLine = true,
+ };
+ View debugView = new View
+ {
+ BackgroundColor = color,
+ Opacity = DebugOpacity,
+ ClippingMode = ClippingModeType.ClipChildren,
+ };
+ debugView.Add(debugText);
+ debugView.Hide();
+ return debugView;
+ }
+
+ debugLeftMargin = GetDebugContainer(Color.Red, "LEFT\nMARGIN");
+ debugMiddleContent = GetDebugContainer(Color.Green, "MARGINAL\nCONTENT");
+ debugRightMargin = GetDebugContainer(Color.Blue, "RIGHT\nMARGIN");
+
+ base.Add(debugLeftMargin);
+ base.Add(debugMiddleContent);
+ base.Add(debugRightMargin);
+ }
+
+ private void RelayoutDebug(int margin)
+ {
+ debugLeftMargin.Position2D = new Position2D(0, 0);
+ debugLeftMargin.Size2D = new Size2D(margin, Size2D.Height);
+
+ debugMiddleContent.Position2D = new Position2D(margin, 0);
+ debugMiddleContent.Size2D = new Size2D(marginalWidth, Size2D.Height);
+
+ debugRightMargin.Position2D = new Position2D(margin + marginalWidth, 0);
+ debugRightMargin.Size2D = new Size2D(margin, Size2D.Height);
+ }
+ }
+}
xmlns:views="clr-namespace:Fitness.Views"
xmlns:ctrl="clr-namespace:Fitness.Controls"
xmlns:nui="clr-namespace:Tizen.NUI.Components;assembly=Tizen.NUI.Components"
+ xmlns:sui="clr-namespace:ScalableUI"
xmlns:converters="clr-namespace:Fitness.Views.Converters"
xmlns:styles="clr-namespace:Fitness.Views.Styles"
xmlns:behaviors="clr-namespace:Fitness.Views.Behaviors"
ParentOrigin="Center"
PivotPoint="Center"
PositionUsesPivotPoint="true">
-
+
<View.BindingContext>
<vm:MainViewModel x:Name="context"/>
</View.BindingContext>
<LinearLayout LinearOrientation="Vertical"/>
</View.Layout>
- <nui:Button BindingContext="{Binding Source={x:Reference context}}"
- Size="{views:SizeInUnits Width=12, Height=12}"
- Margin="{views:ExtentsInUnits Start=6, Bottom=8}"
- behaviors:StyleSetter.Style="{x:Static styles:Buttons.Exit}"
- Command="{Binding Exit}"/>
+ <!--top level view contains sui:MarginalView and nui:CollectionView-->
- <View Margin="{views:ExtentsInUnits Start=16, End=16}"
- WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
- HeightSpecification="{Static LayoutParamPolicies.MatchParent}">
+ <sui:MarginalView ShowMarginalDebugColors="False"
+ HeightSpecification="{Static LayoutParamPolicies.MatchParent}">
- <View.Layout>
- <LinearLayout LinearOrientation="Horizontal"/>
- </View.Layout>
-
- <ImageView BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- ResourceUrl="{Binding ThumbnailUrl}"
- WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
- HeightSpecification="{Static LayoutParamPolicies.MatchParent}"
- x:Name="imagePreview"
- ctrl:Connected.Id="preview">
-
- <ImageView.Layout>
- <LinearLayout LinearOrientation="Vertical"
- LinearAlignment="Center"
- CellPadding="{views:SizeInUnits Width=0, Height=10}"/>
- </ImageView.Layout>
-
- <ctrl:NinePatchButton BindingContext="{Binding Source={x:Reference context}}"
- PositionUsesPivotPoint="true"
- ParentOrigin="0.5, 0.5"
- PivotPoint="0.5, 0.5"
- Text="Let's try!"
- Command="{Binding StartWorkout}"
- Size="{views:SizeInUnits Width=112, Height=24}"
- behaviors:StyleSetter.Style="{x:Static styles:Buttons.Inverse}"/>
-
- <ctrl:NinePatchButton BindingContext="{Binding Source={x:Reference context}}"
- PositionUsesPivotPoint="true"
- ParentOrigin="0.5, 0.5"
- PivotPoint="0.5, 0.5"
- Text="Watch preview"
- Command="{Binding WatchPreview}"
- Size="{views:SizeInUnits Width=112, Height=24}"
- behaviors:StyleSetter.Style="{x:Static styles:Buttons.Regular}"/>
-
- </ImageView>
-
- <View Size="{views:SizeInUnits Width=114}"
+ <!--MarginalView must contain single view-->
+ <View WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
HeightSpecification="{Static LayoutParamPolicies.MatchParent}">
-
<View.Layout>
- <AbsoluteLayout/>
+ <LinearLayout LinearOrientation="Vertical"
+ LinearAlignment="Top"/>
</View.Layout>
- <View>
-
+ <nui:Button BindingContext="{Binding Source={x:Reference context}}"
+ Size="{views:SizeInUnits Width=12, Height=12}"
+ Margin="{views:ExtentsInUnits Start=6, Bottom=8}"
+ behaviors:StyleSetter.Style="{x:Static styles:Buttons.Exit}"
+ Command="{Binding Exit}"/>
+
+ <View WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
+ HeightSpecification="{Static LayoutParamPolicies.MatchParent}">
+
<View.Layout>
- <LinearLayout LinearOrientation="Vertical"
- LinearAlignment="Begin"/>
+ <LinearLayout LinearOrientation="Horizontal"/>
</View.Layout>
-
- <View Margin="{views:ExtentsInUnits Start=6, Top=3}">
-
- <View.Layout>
- <LinearLayout LinearOrientation="Horizontal"
- LinearAlignment="Begin"/>
- </View.Layout>
-
- <TextLabel BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- Text="{Binding Path=Title}"
- PixelSize="{views:PixelSizeInUnits UnitSize=10}"
- VerticalAlignment="Center"
- TextColor="#000C2B">
- <TextLabel.FontStyle>
- <PropertyMap>
- <KeyValue Key="weight" Value="regular" />
- </PropertyMap>
- </TextLabel.FontStyle>
- </TextLabel>
- <ImageView BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- Size="{views:SizeInUnits Width=9, Height=12}"
- Margin="{views:ExtentsInUnits Start=7, End=7}"
- ResourceUrl="{Binding Difficulty, Converter={Static converters:DifficultyLevelToIconConverter.Converter}}"/>
-
- </View>
-
- <View Margin="{views:ExtentsInUnits Start=6, Top=4}">
-
+ <ImageView BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
+ ResourceUrl="{Binding ThumbnailUrl}"
+ WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
+ HeightSpecification="{Static LayoutParamPolicies.MatchParent}"
+ x:Name="imagePreview"
+ ctrl:Connected.Id="preview"
+
+ FittingMode="ShrinkToFit">
+
+ <ImageView.Layout>
+ <LinearLayout LinearOrientation="Vertical"
+ LinearAlignment="Center"
+ CellPadding="{views:SizeInUnits Width=0, Height=10}"/>
+ </ImageView.Layout>
+
+ <ctrl:NinePatchButton BindingContext="{Binding Source={x:Reference context}}"
+ PositionUsesPivotPoint="true"
+ ParentOrigin="0.5, 0.5"
+ PivotPoint="0.5, 0.5"
+ Text="Let's try!"
+ Command="{Binding StartWorkout}"
+ Size="{views:SizeInUnits Width=112, Height=24}"
+ behaviors:StyleSetter.Style="{x:Static styles:Buttons.Inverse}"/>
+
+ <ctrl:NinePatchButton BindingContext="{Binding Source={x:Reference context}}"
+ PositionUsesPivotPoint="true"
+ ParentOrigin="0.5, 0.5"
+ PivotPoint="0.5, 0.5"
+ Text="Watch preview"
+ Command="{Binding WatchPreview}"
+ Size="{views:SizeInUnits Width=112, Height=24}"
+ behaviors:StyleSetter.Style="{x:Static styles:Buttons.Regular}"/>
+
+ </ImageView>
+
+ <View Size="{views:SizeInUnits Width=114}"
+ HeightSpecification="{Static LayoutParamPolicies.MatchParent}">
+
<View.Layout>
- <LinearLayout LinearOrientation="Horizontal"
- LinearAlignment="Begin"/>
+ <AbsoluteLayout/>
</View.Layout>
-
- <ImageView Size="{views:SizeInUnits Width=7, Height=7}"
- ResourceUrl="*Resource*/layout/images/icon_time.png"/>
-
+
+ <View>
+
+ <View.Layout>
+ <LinearLayout LinearOrientation="Vertical"
+ LinearAlignment="Begin"/>
+ </View.Layout>
+
+ <View Margin="{views:ExtentsInUnits Start=6, Top=3}">
+
+ <View.Layout>
+ <LinearLayout LinearOrientation="Horizontal"
+ LinearAlignment="Begin"/>
+ </View.Layout>
+
+ <TextLabel BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
+ Text="{Binding Path=Title}"
+ PixelSize="{views:PixelSizeInUnits UnitSize=10}"
+ VerticalAlignment="Center"
+ TextColor="#000C2B">
+ <TextLabel.FontStyle>
+ <PropertyMap>
+ <KeyValue Key="weight" Value="regular" />
+ </PropertyMap>
+ </TextLabel.FontStyle>
+ </TextLabel>
+
+ <ImageView BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
+ Size="{views:SizeInUnits Width=9, Height=12}"
+ Margin="{views:ExtentsInUnits Start=7, End=7}"
+ ResourceUrl="{Binding Difficulty, Converter={Static converters:DifficultyLevelToIconConverter.Converter}}" />
+
+ </View>
+
+ <View Margin="{views:ExtentsInUnits Start=6, Top=4}">
+
+ <View.Layout>
+ <LinearLayout LinearOrientation="Horizontal"
+ LinearAlignment="Begin"/>
+ </View.Layout>
+
+ <ImageView Size="{views:SizeInUnits Width=7, Height=7}"
+ ResourceUrl="*Resource*/layout/images/icon_time.png" />
+
+ <TextLabel BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
+ Text="{Binding Duration, StringFormat=\{0:h\\:mm\\:ss\}}"
+ PixelSize="{views:PixelSizeInUnits UnitSize=7}"
+ Margin="{views:ExtentsInUnits Start=2}"
+ VerticalAlignment="Center"
+ TextColor="#000C2B">
+ <TextLabel.FontStyle>
+ <PropertyMap>
+ <KeyValue Key="weight" Value="regular"/>
+ </PropertyMap>
+ </TextLabel.FontStyle>
+ </TextLabel>
+
+ </View>
+
+ </View>
+
+ <ImageView PositionUsesPivotPoint="true"
+ PivotPoint="1.0, 0.0"
+ ParentOrigin="1.0, 0.0"
+ Size="{views:SizeInUnits Width=10, Height=10}"
+ BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
+ ResourceUrl="{Binding Favourite, Converter={Static converters:FavouriteToIconConverter.Converter}}"/>
+
<TextLabel BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- Text="{Binding Duration, StringFormat=\{0:h\\:mm\\:ss\}}"
+ Padding="{views:ExtentsInUnits Start=4}"
+ WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
+ Text="{Binding Description}"
PixelSize="{views:PixelSizeInUnits UnitSize=7}"
- Margin="{views:ExtentsInUnits Start=2}"
- VerticalAlignment="Center"
- TextColor="#000C2B">
+ LineWrapMode="Mixed"
+ Weight="1.0"
+ Ellipsis="false"
+ MultiLine="true"
+ TextColor="#000C2B"
+ PositionUsesPivotPoint="true"
+ PivotPoint="BottomLeft"
+ ParentOrigin="BottomLeft">
<TextLabel.FontStyle>
<PropertyMap>
- <KeyValue Key="weight" Value="regular" />
+ <KeyValue Key="weight" Value="regular"/>
</PropertyMap>
</TextLabel.FontStyle>
</TextLabel>
</View>
-
</View>
- <ImageView PositionUsesPivotPoint="true"
- PivotPoint="1.0, 0.0"
- ParentOrigin="1.0, 0.0"
- Size="{views:SizeInUnits Width=10, Height=10}"
- BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- ResourceUrl="{Binding Favourite, Converter={Static converters:FavouriteToIconConverter.Converter}}"/>
-
- <TextLabel BindingContext="{Binding Source={x:Reference context}, Path=SelectedWorkout}"
- Padding="{views:ExtentsInUnits Start=4}"
- WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
- Text="{Binding Description}"
- PixelSize="{views:PixelSizeInUnits UnitSize=7}"
- LineWrapMode="Mixed"
- Weight="1.0"
- Ellipsis="false"
- MultiLine="true"
- TextColor="#000C2B"
- PositionUsesPivotPoint="true"
- PivotPoint="BottomLeft"
- ParentOrigin="BottomLeft">
- <TextLabel.FontStyle>
- <PropertyMap>
- <KeyValue Key="weight" Value="regular" />
- </PropertyMap>
- </TextLabel.FontStyle>
- </TextLabel>
-
- </View>
- </View>
+ </View>
+ </sui:MarginalView>
<nui:CollectionView Size="{views:SizeInUnits Height=44}"
Margin="{views:ExtentsInUnits Top=10}"
<views:FitnessItemView />
</DataTemplate>
</nui:CollectionView.ItemTemplate>
-
+
<nui:CollectionView.ItemsLayouter>
<nui:GridLayouter />
</nui:CollectionView.ItemsLayouter>
-
+
</nui:CollectionView>
</ctrl:Page>