New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stylesheets #1207

Merged
merged 3 commits into from Dec 20, 2017

Conversation

Projects
None yet
9 participants
@StephaneDelcroix
Member

StephaneDelcroix commented Oct 18, 2017

Description of Change

Support cascading StyleSheets.
Support all selector (no media selectors)

Preliminary note

This code and feature is brand new. The API shouldn't change much, but some of the behaviors might be slightly altered in the future, depending on the feedback received.

Supported Selectors

Selector Example Description
.class .header Selects all elements with the StyleClass property containing 'header'
#id #email Selects all elements with StyleId set to email. If StyleId is not set, fallback to x:Name. When using Xaml, always prefer x:Name over StyleId.
* * Selects all elements
element label Selects all elements of type Label (but not subclasses). Case irrelevant.
^base ^contentpage Selects all elements with ContentPage as base class, including ContentPage itself. Case irrelevant. This selector isn't present in the CSS specification, and only applies to XF.
element,element label,button Selects all Buttons and all Labels
element element stacklayout label Selects all Labels inside of a StackLayout
element>element stacklayout>label Selects all Labels with StackLayout as a direct parent
element+element label+entry Selects all Entries directly after a Label
element~element label~entry Selects all Entries preceded by a Label

Unsupported Selectors (for this version)

  • [attribute] selectors
  • @media or @supportsselectors
  • : or :: selectors

Selector combinations

Selectors can be combined without limitation, like in StackLayout > ContentView > label.email. But keep it sane !

Precedence

Styles with matching selectors are all applied, one by one, in definition order. Styles defined on the item itself is always applied last.

This is the expected behavior in most cases, even if doesn't 100% match common CSS implementations.

Specificity, and specificity overrides (!important) are not supported. This is a known issue.

Supported properties

Property Applies to Values (string literals are grayed, while types are italic) Example
background-color VisualElement color (see Colors) | initial background-color: springgreen;
background-image Page string | initial background-image: bg.png;
border-color Button, Frame color (see Colors) | initial border-color: #9acd32;
border-width Button double | initial border-width: .5;
color Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker color (see Colors) | initial color: rgba(255, 0, 0, 0.3);
direction VisualElement ltr | rtl | inherit, initial direction: rtl;
font-family Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span string | initial font-family: Consolas;
font-size Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span double | namedsize (see NamedSize) | initial font-size: 12;
font-style Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span bold | italic | initial font-style: bold;
height VisualElement double | initial `min-height: 250;
margin View thickness (see Thickness) | initial margin: 6 12;
margin-left View thickness (see Thickness) | initial margin-left: 3;
margin-top View thickness (see Thickness) | initial margin-top: 2;
margin-right View thickness (see Thickness) | initial margin-right: 1;
margin-bottom View thickness (see Thickness) | initial margin-bottom: 6;
min-height VisualElement double | initial `min-height: 50;
min-width VisualElement double | initial `min-width: 112;
opacity VisualElement double | initial opacity: .3;
padding Layout, Page thickness (see Thickness) | initial padding: 6 12 12;
padding-left Layout, Page double | initial padding-left: 3;
padding-top Layout, Page double | initial padding-top: 4;
padding-right Layout, Page double | initial padding-right: 2;
padding-bottom Layout, Page double | initial padding-bottom: 6;
text-align Entry, EntryCell, Label, SearchBar left | right | center | start | end | initial . It is recommended to avoid left and right in non-ltr environments text-align: right;
visibility VisualElement true | visible | false | hidden | collapse | initial visibility: hidden;
width VisualElement double | initial `min-width: 320;

Unsupported Common Properties

  • all: initial,
  • layout properties (box, or grid). FlexLayout is coming, and it'll be CSS stylable,
  • shorthand properties, like font, border

Colors

  • one of the 140 X11 color (https://en.wikipedia.org/wiki/X11_color_names), which happens to match CSS Colors, UWP predefined colors and XF Colors. Case insensitive
  • HEX: #rgb, #argb, #rrggbb, #aarrggbb
  • RGB: rgb(255,0,0), rgb(100%,0%,0%) => values in range 0-255 or 0%-100%
  • RGBA: rgba(255, 0, 0, 0.8), rgba(100%, 0%, 0%, 0.8) => opacity is 0.0-1.0
  • HSL: hsl(120, 100%, 50%) => h is 0-360, s and l are 0%-100%
  • HSLA: hsla(120, 100%, 50%, .8) => opacity is 0.0-1.0

Thickness

One, two, three or four values, separated by white spaces.

  • a single value indicates uniform thickness
  • two values indicates (resp.) vertical and horizontal thickness
  • three values indicates (resp.) top, horizontal (left and right) and bottom thickness
  • when using four values, they are top, right, bottom, left

IMPORTANT: This differs from Xaml thickness definitions, which are

  1. separated by commas (,),
  2. are in the form of uniform, horizontal, vertical or left, top, right, bottom.

NamedSize

One of the following value, case insensitive. Exact meaning depends of the platform and the control

  • default,
  • micro,
  • small,
  • medium,
  • large

Initial

initial is a valid value for all properties. It clears the value (resets to default) that was set from another Style.

Additional remarks

  • no inheritance supported, meaning no inherit value and that you can't set the font-size to a layout, and expect all the labels in that layout to inherit the value. The only exception is the direction property, which supports inherit, and that's the default value.
  • element are matched by name, no support for xmlns

Usage

XAML (preferred)
<ContentPage x:Class="...">
  <ContentPage.Resources>
    <StyleSheet Source="appresources/style.css" />
  </ContentPage.Resources>
</ContentPage>

the Source argument takes an Uri relative to the current xaml control, or relative to the application root if it starts with a /. The style.css has to be an EmbeddedResource.

alternatively, you can inline your style in a CDATA Section

<ContentPage x:Class="...">
  <ContentPage.Resources>
    <StyleSheet>
<![CDATA[
^contentpage {
    background-color: orange;
    padding: 20;
}

stacklayout > * {
    margin: 3;
}
]]>
    </StyleSheet>
  </ContentPage.Resources>
</ContentPage>

do not abuse of that second syntax.

in C#

From an embedded resource:

myPage.Resources.Add(StyleSheet.FromAssemblyResource(this.GetType().Assembly, "resource.id.of.the.css"));

or from a TextReader:

using (var reader = new StringReader(my_css_string))
    myPage.Resources.Add(StyleSheet.FromReader(reader));

StyleSheet, XamlC and other potential optimizations

At this time, CSS StyleSheets are parsed and evaluated at runtime. That aren't compiled. Every time a StyleSheet is used, it's reparsed again. If parsing time is an issue, enabling caching is trivial, but comes at memory cost.

Bugs Fixed

None. but It'll introduce some new ones

API Changes

Added

Not that much new API...

namespace Xamarin.Forms.StyleSheets{
    public sealed class StyleSheet {
        public static StyleSheet FromAssemblyResource(Assembly assembly, string resourceId, IXmlLineInfo lineInfo = null);
        public static StyleSheet FromReader(TextReader reader);
    }
}
namespace Xamarin.Forms {
    public class ResourceDictionary {
        void Add(StyleSheet);
    }

    public class Frame {
        public static readonly BindableProperty BorderColorProperty;
        public Color BorderColor { get; set; }
    }
}

Obsoleted

Frame.OutlineColor { get; set; }
Frame.OutlineColorProperty;

Behavioral Changes

/

PR Checklist

  • Has tests (if omitted, state reason in description)
  • Rebased on top of master at time of PR
  • Changes adhere to coding standard
  • Consolidate commits as makes sense
Show outdated Hide outdated .nuspec/Xamarin.Forms.targets Outdated
Show outdated Hide outdated Xamarin.Forms.Core/Properties/AssemblyInfo.cs Outdated
Show outdated Hide outdated Xamarin.Forms.Core/VisualElement_StyleSheet.cs Outdated
Show outdated Hide outdated .nuspec/Xamarin.Forms.targets Outdated
[TestCase("page>stacklayout>contentview>label", false, false, true, false, false, false)]
[TestCase("visualelement", false, false, false, false, false, false)]
[TestCase("^visualelement", true, true, true, true, true, true)]
[TestCase("^layout", false, false, false, false, false, true)]

This comment has been minimized.

@StephaneDelcroix

StephaneDelcroix Nov 23, 2017

Member

this is how me make CSS support language with inheritance

@StephaneDelcroix

StephaneDelcroix Nov 23, 2017

Member

this is how me make CSS support language with inheritance

@tpetrina

This comment has been minimized.

Show comment
Hide comment
@tpetrina

tpetrina Dec 1, 2017

Contributor

I see that there are individual properties for padding, but not for margin. Why is that? [UPDATE: FIXED]

Also, can we nest selectors arbitrary many times? Like grid > grid > label.title.

Contributor

tpetrina commented Dec 1, 2017

I see that there are individual properties for padding, but not for margin. Why is that? [UPDATE: FIXED]

Also, can we nest selectors arbitrary many times? Like grid > grid > label.title.

@StephaneDelcroix

This comment has been minimized.

Show comment
Hide comment
@StephaneDelcroix

StephaneDelcroix Dec 1, 2017

Member

@tpetrina

I see that there are individual properties for padding, but not for margin. Why is that?

nice catch, I'll add them

Also, can we nest selectors arbitrary many times? Like grid > grid > label.title

yes, you can. see https://github.com/xamarin/Xamarin.Forms/pull/1207/files#diff-73f8da31e9d7780757345a55024ff7eaR99 for a test. The "Selector Combination" part of the PR description should state that, but it's still empty

Member

StephaneDelcroix commented Dec 1, 2017

@tpetrina

I see that there are individual properties for padding, but not for margin. Why is that?

nice catch, I'll add them

Also, can we nest selectors arbitrary many times? Like grid > grid > label.title

yes, you can. see https://github.com/xamarin/Xamarin.Forms/pull/1207/files#diff-73f8da31e9d7780757345a55024ff7eaR99 for a test. The "Selector Combination" part of the PR description should state that, but it's still empty

@davidortinau davidortinau added this to In Review in v3.1.0 Dec 7, 2017

@samhouts

samhouts approved these changes Dec 12, 2017 edited

This looks really good.

The only things that are unclear to me are:

  1. Are there explicit tests for css precedence when two styles match the same element with the same level of specificity? In this case, the latest style should take precedence. And where are you determining "latest style" if the styles are imported from multiple sources (inline should trump resources, etc.)?
  2. Do we support !important?
  3. Do we support putting css on an element itself? style="color: red"? Do we have tests for precedence here?

We need to highlight the breaking changes and deprecations in the PR. OutlineColor stands out to me.

And the docs dll version will probably need to change before long. :)

Also, I'm sad that we don't support LESS. :trollface:

Show outdated Hide outdated Xamarin.Forms.Build.Tasks/CompiledConverters/RDSourceTypeConverter.cs Outdated
IList<string> IStyleSelectable.Classes => null;
string IStyleSelectable.Id => StyleId;

This comment has been minimized.

@samhouts

samhouts Dec 12, 2017

Member

K, I have questions. We have previously instructed users to use this property to uniquely identify an Element for UI testing. We changed our own implementation to use AutomationId, which maps to entirely different properties, but is it possible that using StyleId in this context is now a conflicting, if not breaking, change? Will the values we set here still be unique? Will we override values set by the user? If the user sets this value to something strange, how will the style parsing engine react?

ref: https://developer.xamarin.com/api/property/Xamarin.Forms.Element.StyleId/

@samhouts

samhouts Dec 12, 2017

Member

K, I have questions. We have previously instructed users to use this property to uniquely identify an Element for UI testing. We changed our own implementation to use AutomationId, which maps to entirely different properties, but is it possible that using StyleId in this context is now a conflicting, if not breaking, change? Will the values we set here still be unique? Will we override values set by the user? If the user sets this value to something strange, how will the style parsing engine react?

ref: https://developer.xamarin.com/api/property/Xamarin.Forms.Element.StyleId/

This comment has been minimized.

@StephaneDelcroix

StephaneDelcroix Dec 13, 2017

Member

we could only use x:Name. let's bring that in a discussion one of these days

@StephaneDelcroix

StephaneDelcroix Dec 13, 2017

Member

we could only use x:Name. let's bring that in a discussion one of these days

Show outdated Hide outdated Xamarin.Forms.Core/StyleSheets/CssReader.cs Outdated
Show outdated Hide outdated Xamarin.Forms.Core/StyleSheets/CssReader.cs Outdated
Show outdated Hide outdated Xamarin.Forms.Platform.Android/Renderers/FrameRenderer.cs Outdated
@StephaneDelcroix

This comment has been minimized.

Show comment
Hide comment
@StephaneDelcroix

StephaneDelcroix Dec 13, 2017

Member

@samhouts

Are there explicit tests for css precedence when two styles match the same element with the same level of specificity?

we do not support specificity yet. All styles with a matching selector are applied in the order in which they're defined

Do we support !important?

we don't, on purpose

Do we support putting css on an element itself?

Even in the HTML world, this is considered very bad practice (quoting MDN here). But you can set the StyleSheet to inline data to any element.

Member

StephaneDelcroix commented Dec 13, 2017

@samhouts

Are there explicit tests for css precedence when two styles match the same element with the same level of specificity?

we do not support specificity yet. All styles with a matching selector are applied in the order in which they're defined

Do we support !important?

we don't, on purpose

Do we support putting css on an element itself?

Even in the HTML world, this is considered very bad practice (quoting MDN here). But you can set the StyleSheet to inline data to any element.

StephaneDelcroix added a commit that referenced this pull request Dec 15, 2017

[*] Support for CSS StyleSheets
 Parsing, loading, and applying of StyleSheets

 see #1207 (comment)
 for complete description.

StephaneDelcroix added some commits Nov 3, 2017

[*] Support for CSS StyleSheets
 Parsing, loading, and applying of StyleSheets

 see #1207 (comment)
 for complete description.

@xamarin xamarin deleted a comment from rmarinho Dec 19, 2017

@StephaneDelcroix StephaneDelcroix merged commit 5ea86a2 into master Dec 20, 2017

3 of 11 checks passed

VSTS: Android API19 Validation Legacy Renderers UITests Running
Details
VSTS: Android API23 Validation Fast Renderers UITests Running
Details
VSTS: Android API23 Validation Legacy Renderers UITests Running
Details
VSTS: Android API25 Validation Fast Renderers UITests Running
Details
VSTS: Android API25 Validation Legacy Renderers UITests Running
Details
VSTS: iOS10 Validation UITests Running
Details
VSTS: iOS11 Validation UITests Running
Details
VSTS: iOS9 Validation UITests Running
Details
VSTS: Xamarin Forms OSX PR-1207 - (1238121) succeeded
Details
VSTS: Xamarin Forms Windows VS2017 1238076 Succeeded
Details
license/cla All CLA requirements met.
Details

v3.1.0 automation moved this from In Review to Done Dec 20, 2017

@StephaneDelcroix StephaneDelcroix deleted the StyleSheet branch Dec 20, 2017

jsuarezruiz added a commit to jsuarezruiz/Xamarin.Forms that referenced this pull request Dec 21, 2017

Merge branch 'master' of https://github.com/xamarin/Xamarin.Forms
* 'master' of https://github.com/xamarin/Xamarin.Forms: (53 commits)
  Add implicit color conversion to .NET Standard's System.Drawing.Color (xamarin#1359)
  [WPF, GTK, TIZEN] Replace obsolete OutlineColor by BorderColor (xamarin#1443)
  Stylesheets (xamarin#1207)
  [WPF] Add Deserializer implementation (xamarin#1411)
  [Build]Update provisioning submodule
  [Build] Update provisioning
  [UWP] Optimized ZIndexing for a thirty-nine percent speed boot on loa… (xamarin#1196)
  [Xaml] execute XamlG and XamlC on UWP SAP (xamarin#1391)
  [iOS] Update status bar color while page is appearing (xamarin#1288)
  Add condition SdkInt >= JellyBeanMr1 for set flow direction (xamarin#1388)
  Use GlobalAssemblyInfo on tizen backend (xamarin#1397)
  [Build] Bump to 15.5.2
  revert part of xamarin#1370. keep the test project out of netstandard for now (xamarin#1402)
  fix typo in csproj
  Fix for netstandard projects (xamarin#1382)
  [Xaml] open the api for loading xaml from string (xamarin#1394)
  [Android] Prevent a custom Entry/Editor Drawable from modifying non-custom ones (xamarin#1197)
  [build] force build on vs2015
  Add Xamarin.Forms.ControlGallery.Tizen project (xamarin#1389)
  Move XF.Build.Tasks to nestandard2.0 (xamarin#1370)
  ...
{
public static readonly BindableProperty OutlineColorProperty = BindableProperty.Create("OutlineColor", typeof(Color), typeof(Frame), Color.Default);
[Obsolete("OutlineColorProperty is obsolete as of version 3.0.0. Please use BorderColorProperty instead.")]

This comment has been minimized.

@alanmcgovern

alanmcgovern Jan 12, 2018

Contributor

I think this change caused #1566

@alanmcgovern

alanmcgovern Jan 12, 2018

Contributor

I think this change caused #1566

@ali-h2010

This comment has been minimized.

Show comment
Hide comment
@ali-h2010

ali-h2010 Apr 17, 2018

I am not sure if someone already asked about this before but if someone did, please guide me to the answer.

How to add different CSS for tablets like the iPad or android tablets in general?
Currently , if you run the app in a big screen device , everything will be stretched and most of the time will not be acceptable in a professional environment.

so, I had to do many steps to solve this issue such as:

  • bind the padding values and width of some views
  • use this condition checking to check if device is a phone or tablet

`if (Device.Idiom == TargetIdiom.Phone)
{
HomeIconWidth = 70;
HomeIconHeight = 70;

            HomeIconFrameWidth = 160;
            HomeIconFrameHeight = 160;

            NumberOfApprovalsLayout = new Rectangle(120, 10, 30, 30);
        }
        else
        {
            HomeIconWidth = 110;
            HomeIconHeight = 110;

            HomeIconFrameWidth = 200;
            HomeIconFrameHeight = 200;

            NumberOfApprovalsLayout = new Rectangle(160, 10, 30, 30);
        }`

As you can see it's a lot of variables and conditions to set for every page.
Even if I use styles , I couldn't find a way to check the display in Xaml and apply the correct style.

Is this issue still there or does CSS provide a solution to that?
if there is a solution ,from the look of it it won't be like the web development CSS since @media is not supported like the one that bootstrap uses.

UPDATE:

NeverMind that long post
I found this:

<StackLayout.Orientation> <OnIdiom x:TypeArguments="StackOrientation"> <OnIdiom.Phone>Vertical</OnIdiom.Phone> <OnIdiom.Tablet>Horizontal</OnIdiom.Tablet> </OnIdiom> </StackLayout.Orientation>

ali-h2010 commented Apr 17, 2018

I am not sure if someone already asked about this before but if someone did, please guide me to the answer.

How to add different CSS for tablets like the iPad or android tablets in general?
Currently , if you run the app in a big screen device , everything will be stretched and most of the time will not be acceptable in a professional environment.

so, I had to do many steps to solve this issue such as:

  • bind the padding values and width of some views
  • use this condition checking to check if device is a phone or tablet

`if (Device.Idiom == TargetIdiom.Phone)
{
HomeIconWidth = 70;
HomeIconHeight = 70;

            HomeIconFrameWidth = 160;
            HomeIconFrameHeight = 160;

            NumberOfApprovalsLayout = new Rectangle(120, 10, 30, 30);
        }
        else
        {
            HomeIconWidth = 110;
            HomeIconHeight = 110;

            HomeIconFrameWidth = 200;
            HomeIconFrameHeight = 200;

            NumberOfApprovalsLayout = new Rectangle(160, 10, 30, 30);
        }`

As you can see it's a lot of variables and conditions to set for every page.
Even if I use styles , I couldn't find a way to check the display in Xaml and apply the correct style.

Is this issue still there or does CSS provide a solution to that?
if there is a solution ,from the look of it it won't be like the web development CSS since @media is not supported like the one that bootstrap uses.

UPDATE:

NeverMind that long post
I found this:

<StackLayout.Orientation> <OnIdiom x:TypeArguments="StackOrientation"> <OnIdiom.Phone>Vertical</OnIdiom.Phone> <OnIdiom.Tablet>Horizontal</OnIdiom.Tablet> </OnIdiom> </StackLayout.Orientation>

@samhouts samhouts added this to the 3.0.0 milestone May 5, 2018

@samhouts samhouts removed this from Done in v3.1.0 May 8, 2018

@mmacneil

This comment has been minimized.

Show comment
Hide comment
@mmacneil

mmacneil May 11, 2018

Can styles on an element be changed at runtime? They are applied as expected on initial load but I seem unable to dynamically change them at runtime based on some event. For example, in a page code-behind this scenario:

void ButtonClick() { myPage.MyCoolLabel.StyleClass = new List<string> { "someNewStyle" }; }

This has no effect, and does not update MyCoolLabel with new styling.

mmacneil commented May 11, 2018

Can styles on an element be changed at runtime? They are applied as expected on initial load but I seem unable to dynamically change them at runtime based on some event. For example, in a page code-behind this scenario:

void ButtonClick() { myPage.MyCoolLabel.StyleClass = new List<string> { "someNewStyle" }; }

This has no effect, and does not update MyCoolLabel with new styling.

@StephaneDelcroix

This comment has been minimized.

Show comment
Hide comment
@StephaneDelcroix

StephaneDelcroix May 11, 2018

Member

@mmacneil please open an issue, we will discuss that over there

Member

StephaneDelcroix commented May 11, 2018

@mmacneil please open an issue, we will discuss that over there

@mmacneil

This comment has been minimized.

Show comment
Hide comment
@mmacneil

mmacneil May 11, 2018

Done: 2678

Thanks Stephane

mmacneil commented May 11, 2018

Done: 2678

Thanks Stephane

@samhouts samhouts modified the milestones: 3.0.0, 2.3.0 Jun 27, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment