Skip to content

havrlisan/zx-skiacomponents

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logo

Delphi 11+ support

An open-source set of Delphi components for the FireMonkey framework that utilizes the Skia4Delphi library.

Summary

Base controls

ZX defines the TZxCustomControl and TZxStyledControl classes, which inherit from TSkCustomControl and TSkStyledControl, respectively. They implement only a workaround for an FMX behavior on mobile platforms: panning and releasing execute Click on the control on which you released your finger. This is especially annoying when you're scrolling through clickable components. The most simple workaround I found was the following:

procedure TZxCustomControl.Click;
begin
{$IFDEF ZX_FIXMOBILECLICK}
  if FManualClick then
{$ENDIF}
    inherited;
  DoClick;
end;

procedure TZxCustomControl.Tap(const Point: TPointF);
begin
{$IFDEF ZX_FIXMOBILECLICK}
  FManualClick := True;
  Click;
  FManualClick := False;
{$ENDIF}
  inherited;
  DoTap(Point);
end;

This way, Click only executes from the Tap method, and Tap is executed only if the internal calculation concluded it was a tap, not a pan. With this implementation, it is enough to assign only OnClick events for all platforms, instead of assigning both OnClick and OnTap events, and then wrap the OnClick implementation into a compiler directive condition.

Note: I left this feature disabled by default since I'm unsure how the community will receive it (and it may break some existing code). I've also marked both Click and Tap methods as final overrides and added new DoClick and DoTap methods, since otherwise, it is easy to break the fix by overriding the Click method in a child class. To enable it, go to Project > Options > Delphi Compiler, and in Conditional defines add ZX_FIXMOBILECLICK.

Svg components

Thanks to skia4delphi, we have a proper SVG support in Delphi!

TZxSvgGlyph

ZX provides a new glyph component that draws SVG instead of a bitmap. The class implements the IGlyph interface and is almost the same implementation as in TGlyph (with properties such as AutoHide) but without all the bitmap-related code.

TZxSvgBrushList

This class inherits from TBaseImageList and is a collection of TSkSvgBrush items. This implementation allows you to, for example, attach the TZxSvgBrushList component to a TActionList.ImageList, and then use the brushes by assigning TAction.ImageIndex. To display an item, use the above-mentioned TZxSvgGlyph. Note: The component does not yet have a design-time editor, so currently it is possible to manipulate it only through the Structure view.

Text components

Since the arrival of skia4delphi, we're able to:

  • manipulate more text settings with the TSkTextSettings, such as MaxLines, LetterSpacing, font's Weight and Stretch, etc.
  • define a custom style for text settings, and
  • retrieve the true text size before the draw occurs.

TZxText

Reimplementation of FMX's TText control that implements ISkTextSettings instead of ITextSettings. There are 2 major differences when compared to TText:

  1. The parent class is TZxStyledControl meaning it can be styled through the StyleLookup property. The control expects the TSkStyleTextObject in its style object and applies the text settings according to its StyledSettings property. This way you can define and change your text settings in one place!
  2. It contains a published AutoSize property, which is self-explanatory. If you wish to retrieve the text size in runtime without having the AutoSize property set to True, use the public property ParagraphBounds, which returns a TRectF value.

TZxTextControl

The FMX framework defines TTextControl and TPresentedTextControl classes, which serve as a base for all text controls, such as TButton, TListBoxItem, etc. They rely on their style objects to contain the TText control, whose TextSettings can be modified in the Style Designer. Unfortunately, integrating those features into the mentioned base FMX classes is like getting blood from a stone, so ZX reimplemented the TTextControl into TZxTextControl, which utilizes ISkTextSettings instead of ITextSettings. The implementation is similar, except it contains the AutoSize property. If true, the control will resize to the TZxText.ParagraphBounds and any other objects (depending on their alignment and visibility) from its style resource.

TZxCustomButton

This class implements the same functionality as FMX's TCustomButton, except its property Images is a TBaseImageList instead of TCustomImageList, which allows you to assign both TZxSvgBrushList (mentioned earlier) and TImageList. If using the former, the style resource should contain the TZxSvgGlyph (mentioned earlier) component with StyleName set to 'glyph'.

TZxButton and TZxSpeedButton

These classes are the same as their counterparts, TButton and TSpeedButton.

FMX style objects - revamped

The current FMX's approach to the application style relies on TBitmapLinks that links to a part of a large image. Custom styles can be defined per platform, which allows the developer to bring the styles closer to the platform's native look. I won't go further into the details, as the official documentation covers it well.

Looking at the currently most popular cross-platform applications, such as Discord, WhatsApp, and Slack, all share (almost) the same style across platforms. More applications tend to follow that path, and I am a fan as well: from the designer's standpoint, there is only one style that needs to be worked upon, and from the user's standpoint, I like to have the ability to switch platforms and still feel familiar with the same application. Keep in mind that I'm talking specifically about the application style and not the user interface.

The FMX framework introduces style object classes for defining the bitmap links. Their ancestor class is TCustomStyleObject, which draws a bitmap on the canvas by retrieving the bitmap link from a virtual abstract function GetCurrentLink and getting the actual bitmap from the Source, the large image I mentioned above. Every child class defines its own set of bitmap link published properties (e.g. TActiveStyleObject has SourceLink and ActiveLink) that they return in the GetCurrentLink function, depending on which one is active.

What I find troubling with this implementation is that the FMX has set the bitmap links and the image source as the base of their style object classes, and built upon that are the child classes functionalities. It is impossible to define any other link types (such as TAlphaColor, or since skia4delphi, TSkSvgBrush) and implement a custom drawing to the canvas. Because of that, ZX reimplements the style object classes so that the functionalities are defined first, and then the child classes can implement the drawing in any way they want.

An additional limitation of the bitmap links is the lack of animations. If you take a look at some the FMX's style objects implementation, you'll see that they use the TAnimation internally; but not for doing actual animations. TAnimation, besides its core functionality, provides two additional properties: Trigger and TriggerInverse, which are used to decide when to start the animation (learn more). Some style objects use this, while the animation is set to never start, only to notify the handler that the trigger has occurred.

TZxCustomActiveStyleObject

Style object similar to TActiveStyleObject; it contains a published property ActiveTrigger for defining the trigger type. Unlike its counterpart, it contains no implementation for handling the trigger (e.g. drawing a bitmap link). It also contains a public property Duration, for setting the animation duration.

TZxColorActiveStyleObject

A descendant of TZxCustomActiveStyleObject that draws a colored rectangle (fill color only) depending on the trigger state. When the trigger occurs, the color change is animated through interpolation. To set the colors, use the published properties SourceColor and ActiveColor. The class also contains RadiusX and RadiusY, allowing you to draw a round rectangle.

TZxAnimatedImageActiveStyleObject

Thanks to skia4delphi, we can display animated images through TSkAnimatedImage. This class implements a TSkAnimatedImage whose animation starts when the trigger is executed. There are two possible states, depending on the boolean value of the AniLoop property:

  1. If set to false, the animation starts on the trigger and starts inversed on the inverse trigger, but does not loop, and
  2. if set to true, the animation starts on the trigger in a loop and stops on the inverse trigger. There are also additional properties for the image animation: AniDelay, AniSource, and AniSpeed.

TZxCustomButtonStyleObject

This class implements the functionality of the TButtonStyleObject with the button trigger types (Normal, Hot, Pressed, Focused), but whose animations have a changeable duration through the published Duration property.

TZxColorButtonStyleObject

A descendant of TZxCustomButtonStyleObject that draws a colored rectangle (fill color only) depending on the trigger state. When any of the triggers occur, the animation interpolates the previous trigger color and the new trigger color. To set the trigger colors, use the published properties NormalColor, HotColor, PressedColor, or FocusedColor. The class also contains RadiusX and RadiusY, allowing you to draw a round rectangle.

Adding default style for components

While writing these components, I had trouble finding how to define a default style for a component that:

  1. doesn't require overriding the GetStyleObject method in every class to load the style from a resource, and
  2. works in both runtime and design-time (without opening the unit in which the style is defined).

The current documentation doesn't satisfy the first request, and for the second one, to get the style to show up in runtime, the style resource has to be added to the application (at least that is the only solution I found). What bothered me is that the FMX controls have their default styles and do not load the same way as described in the existing documentation. In pursuit of achieving the same behavior, I realized that the styles are loaded in design-time similar to that in the runtime, except the loading of the TStyleBook component. That gave me the idea to get the design-time's current TStyleContainer through TStyleManager's function ActiveStyle, and then add the custom control's default style to its children list. To implement this, ZX provides a TZxStyleManager class with the following class methods:

class function AddStyles(const ADataModuleClass: TDataModuleClass): IZxStylesHolder; overload;
class function AddStyles(const AStyleContainer: TStyleContainer; const AClone: Boolean): IZxStylesHolder; overload;
class procedure RemoveStyles(const AStylesHodler: IZxStylesHolder);

Note: The first AddStyles procedure with the ADataModuleClass parameter internally creates the data module instance, loops through its children, and for every TStyleBook instance does the same process as the second AddStyles method.

The IZxStyleHolder interface has no exposed methods or properties and is used only as a reference to the added styles which can be removed by calling the method RemoveStyles. The actual instance that implements the interface holds a list of the added styles, which the TZxStyleManager handles and uses for removing the styles from the global style container.

Advantages and disadvantages

The benefits of this implementation are:

  • no need for data resources,
  • no need to override the GetStyleObject method, and
  • the styles are always visible, without needing to open the data module unit in which the style is defined.

Caveats to this implementation are yet to be found.

WARNING: This implementation is not thoroughly tested on all platforms, so there is a possibility you may run into bugs or problems when using this approach. It has been tested with only one platform/collection defined, Default, as ZX's style approach is one-for-all. Feel free to report any issues or suggest improvements!

How to use

The following are the steps to use the TZxStyleManager:

  1. Create a TDataModule unit, add a TStyleBook, and define a style inside.
  2. Add Zx.StyleManager unit in the implementation uses section.
  3. In the implementation section, declare a variable of type IZxStylesHolder.
  4. In the initialization section, call TZxStyleManager.AddStyles with your data module class as the parameter, and store the return value in the previously declared variable.
  5. In the finalization section, call TZxStyleManager.RemoveStyles with the previously declared variable as the parameter.

About

An open-source set of Delphi components for the FireMonkey framework that utilize the Skia4Delphi library.

Resources

License

Stars

Watchers

Forks

Languages