Archive

Archive for October 12, 2017

Smart Mobile Studio and CSS: part 3

October 12, 2017 Leave a comment

In the first article we looked at some ground rules for how Smart deals with CSS. The most important part is how Smart Mobile Studio maps pascal class names to CSS style names. Simple, but extremely effective.

In the second article we looked at how you should write classes to make styling easy. We also talked about code discipline and that you should never use TW3CustomControl directly, because it makes styling time-consuming and cumbersome.

In this article we are going to cover two things: first we are going to look at probably the most powerful feature CSS has to offer, namely cascades. And then we are going to talk a bit about the new theme system we are working on. Please note that the new theme system is not yet available in the alpha releases yet. Like all things it has to go through the testing stage. All our visual controls needs a little adjustment to support the new themes as well, which doesn’t affect you – but it is time-consuming work.

Cascades

Writing a CSS style for your control should be pretty easy if you have read our previous two articles. But do you really want a large, complex and monolithic style? If you have a look at the stylesheet that ships with Smart Mobile Studio (any of them, there are several), you probably agree that it’s not easy to understand at times. Each control have it’s style definition, that part is clear, but every style includes font, backgrounds, text colors, text shadowing, margins, borders, border shadows, gradients (ad nauseum). Long story short: stylesheets like this is hell to maintain and extremely time-consuming to make.

CSS have this cool feature where you can actually take any number of styles and apply them to the same element. This might sound nutty at first but think it through, because it is going to make your like a lot easier:

  • We can isolate the border style separately
  • We can have multiple border styles and pick the ones we want, rather than a single, hardcoded and fixed version
  • We can define the backgrounds, any number of them, as separate styles

That doesn’t sound to bad does it? But wait, there is more!

Remember how I told you that animations are also defined in CSS? Since CSS allows you to add multiple styles to a control, this also means you can define a style with an animation – and then just add it when you want something to happen, and then remove the style when you don’t need it any more.

You have probably seen these spinners that websites use right? So while the website is loading something, a circle or dot keeps rotating to signal that work is being performed in the background? Well, that’s pretty easy to achieve when you understand how cascades work. You just define the animation, use it in a style — and then add that style to your control. When you want to stop this behavior you just remove the style. Thats it!

But let’s start with something simple. Let’s define a border and a background and apply that to a control using code. And remember: styles you add does not exclude the initial style. Like we talked about earlier – Smart will take the pascal classname and use a CSS style with that name. So whatever you add to the control is extra. Which is really powerful!

You probably want to start a new visual project for this one. And remember to pick a theme in the project options (in my case I picked the “Android-HoloLight.,css” theme, just so you know), save the project, then go back into the options and check the “Use custom theme” checkbox. Again exit the dialog and click save – the IDE will now create a copy of whatever theme you picked and give you direct access to it from the IDE.

Remember to check the custom theme in project options

With that out of the way you should now have a fresh, blank visual application with an item called “Custom CSS” in the project manager list. Now double click on that like before so we can get cracking, and go down to the end of the file. Add the following text:

<?pas   const EdgeRounding = "4px";   const clDlgBtnFace = "#ededed"; ?>

.TMyButtonBorder {
  border-radius:  <!--?pas=EdgeRounding?-->;
  border-top:     1px solid rgba(250, 250, 250, 0.7);
  border-left:    1px solid rgba(250, 250, 250, 0.7);
  border-right:   1px solid rgba(240, 240, 240, 0.5);
  border-bottom:  1px solid rgba(240, 240, 240, 0.5);

  -webkit-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
     -moz-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
          box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
}

.TMyButtonBorder:active {
  border-radius:  <!--?pas=EdgeRounding?-->;
  border-top:     1px solid rgba(240, 240, 240, 0.5);
  border-left:    1px solid rgba(240, 240, 240, 0.5);
  border-right:   1px solid rgba(250, 250, 250, 0.7);
  border-bottom:  1px solid rgba(250, 250, 250, 0.7);

  -webkit-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
     -moz-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
          box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
}

.TMyButtonBackground {
  background-color: <!--?pas=clDlgBtnFace?-->;
  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,color-stop(0, rgb(255, 255, 255)),color-stop(1, rgb(240, 240, 240)));
  background-image: -webkit-repeating-linear-gradient(top,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
  background-image: repeating-linear-gradient(to bottom,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
  background-image: -ms-repeating-linear-gradient(top,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
}

.TMyButtonBackground:active {
  background-color: <!--?pas=clDlgBtnFace?-->;
  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,color-stop(0, rgb(231, 231, 231)),color-stop(0.496, rgb(231, 231, 231)),color-stop(0.5, rgb(231, 231, 231)),color-stop(1, rgb(255, 255, 255)));
  background-image: -webkit-repeating-linear-gradient(top,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
  background-image: repeating-linear-gradient(to bottom,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
  background-image: -ms-repeating-linear-gradient(top,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
}

Now this might look like a huge mess, but most of this is gradient coloring. If you look closer you will notice that it’s the exact same gradients but with different browser prefixing. This is to ensure that things look exactly the same no matter what browser people use. Making gradients like this is easy, there are a ton of websites that deals with this. One of my favorites is ColorZilla, which will make all this code for you.

If you don’t know your CSS you might be wondering – what is that :active postfix? You have two declarations with the same name – but one of them has :active appended to it? The active selector (which is the fancy name) simply tells the browser that whenever someone interacts with an element that has this state – it should switch and display the :active one instead. Typically a button will look 3d when it’s not pressed, and sunken when you press it. This is automated and you just need to define how an element should look when it’s pressed via the :active postfix (note: since different controls do different things, “active” can hold different meanings. But for most controls it means when you click it, touch it or otherwise interact with it).

And now for the big question: what on earth is that first segment that looks like pascal code? Well, that is pascal code! All your CSS stylesheets are processed by a scripting engine and only the result is actually given to the linker. So yes indeed, you can write both functions and procedures and use them to make your CSS like easier (take that Adobe!).

What we have done in the pascal snippet is to define a standard rounding value. That way we don’t have to update 300 places where border-radius is set (or you can blank it out if you dont want round edges). We change the constant and it will spread to any style that uses it. Clever huh?

OK, lets use our styles for something fun! What we have here is a nice border definition, both active and non-active, and also a nice background. Let’s use cascades to change how a button looks like!

What is a button anyways

If you switch to Form1 in your application and place a TW3Button on the form, we can start to work with it. The first thing you need to do is to clear out the styleclass so that Smart doesn’t apply the default styling. That way it’s easier to see what happens. So here is how it looks when I just run and compile it:

cascade_01

Now go into the procedure TForm1.InitializeForm() in the unit Form1. And write the following code:

procedure TForm1.InitializeForm;
begin
  inherited;

  // Remove the default styling
  w3button1.StyleClass := '';

  // Add our border
  w3Button1.TagStyle.Add('TMyButtonBorder');

  // And add our background
  w3button1.TagStyle.Add('TMyButtonBackground');

  // Make the font autosize to the container
  w3button1.font.AutoSize := true;
end;

Now save, compile and run and we get the following result:

cascade_02

Suddenly our vanilla Android button have all the majesty of Ubuntu Linux! And all we did was define a couple of styles and then manually add them. We could of course have stuffed all of this into a single, monolithic style – no shame in that, but im sure you agree that by fragmenting border from background, and background from content – we have a lot of power on our hands!

As an experiment:¬†Remove the line that clears the StyleClass string and see what happens. When you click the button the browser actually blends the two backgrounds together! Had we used RGBA values in our background gradients – the browser would have blended the standard theme button with our added styles. It’s pretty frickin’ awesome if you ask me.

Here is a more extensive example of our upcoming Ubuntu Linux theme. This is not yet ready for alpha, but it represents the first theme system where all our controls makes use of multiple styles. It looks and behaves beautifully.

cascade_03

From the labs: A Ubuntu Linux inspired theme that is done using cascading exclusively

Brave new themes

So far we I have written exclusively about things you can do right now. But we are working every single day on Smart Mobile Studio, and right now my primary task is to finish a working prototype of our theme engine. As you can see from the picture above we still have a few controls that needs to be adjusted. In the previous article I mentioned the importance of respecting borders, padding and margins from the stylesheet; well let’s just say that I have learnt that the hard way.

Most of our controls were written with no consideration regarding these things, we use an absolute boxing model after all so we don’t have to. But not having to do something and taking the time to do something is often the difference between quality and fluff. And this time we are doing things right every step of the way.

Much like the effect system (SmartCL.Effects.pas) the theming system makes use of partial classes. This means that it simply doesn’t exist until you include the unit SmartCL.Theme in your project.

With the theme unit included (actually it’s included by the RTL so it’s there no matter what, but it wont be visible unless you include it in your unit scope) TW3CustomControl suddenly gains a couple of properties and methods:

  • ThemeBorder property
  • ThemeBackground property
  • ThemeReset() method

When you create custom controls you can (if you need to) define a style for that control, but this time you don’t need to define borders or backgrounds. A style is now reduced to padding, margins, some font stuff and perhaps shading if you need that. Then simply assign a ThemeBorder and ThemeBackground in the StyleTagObject() method of your control – and you can make your control look and feel at home with everything else using that theme.

Lets look at the standard borders first:

  • btNone
  • btFlatBorder
  • btControlBorder
  • btContainerBorder
  • btButtonBorder
  • btDialogButtonBorder
  • btDecorativeBorder
  • btEditBorder
  • btListBorder
  • btToolContainerBorder
  • btToolButtonBorder
  • btToolControlBorder
  • btToolControlFlatBorder

And then we have pre-defined backgrounds matching these:

  • bsNone
  • bsDisplay
  • bsControl
  • bsContainer
  • bsList
  • bsListItem
  • bsListItemSelected
  • bsEdit
  • bsButton
  • bsDialogButton
  • bsDecorative
  • bsDecorativeInvert
  • bsDecorativeDark
  • bsToolContainer
  • bsToolButton
  • bsToolControl

And as mentioned, you can assign these to any control you like

Same defines, many themes

The cool thing about the new system is that it’s not just one theme. We start with one of course but ultimately all our themes will follow the new styling scheme. The goal is to use pure constants, much like what Delphi did with colors (clBtnFace and so on) so that we only need to change the coloring constants – and then the changes will spread to the whole theme.

You as a Smart Mobile Studio developer don’t need to care about the details. As long as you stick to the normal types listed above, your custom controls will always match whatever theme is being used. And it will always look good and match the theme.

cascade_04

Still¬† few controls to style, but I’m sure you agree that its starting to look nice

Well that has been a rather long introduction to Smart and CSS. I hope you have enjoyed reading it. I will keep you all posted on the progress we make, which is moving ahead very fast these days!

Personally I can’t wait until Smart Mobile Studio 3.0 is ready, and I hope people value the effort we have put into this. And we are just getting started!