Monday, April 26, 2010

Flicker

Some flicker and other nasty things can be caused by excessive or inappropriate coordinate re-calculations while docking. This is partially caused by the flawed Delphi implementation, and is made even worse in further LCL hacks. Since the TDockManager interface has been improved already, much code can be removed from the LCL.

First we should agree that managed and unmanaged docksites require different handling of docking coordinates and other details. When a DockManager is installed in the target site, it will manage the DockRect (visual feedback) and finally will adjust the placement of the dropped control, including dock headers etc., eventually overriding user changes in event handlers. Without an DockManager, some calculations should be performed and reflected in the DockRect, and no final adjustment should be performed - this would be fooling the user. Also the calculation of BoundsForNewParent is questionable, at least for managed sites. Premature changes of the BoundsRect are a bad idea at all.

I'll outline the required changes to the TControl and TWinControl classes in futher comments to this entry, based on Lazarus revision 24970.

Docking Forms

Since most widgetsets do not (yet) allow to dock windows in the LCL, a commonly usable workaround must be found. Affected are all controls that eat up mouse events themselves, or which do not create events for the non-client area.

A stable and widgetset-independent solution requires a dedicated "dock grip" component, integrated into the forms or controls. That component can react on mouse moves with the left button down, by starting the dragging of the associated control; this can be the control's Parent (form, toolbar), the page of a notebook, or the control in a dock-zone.

My first approach used a red pin icon in the client area of a form, but this turned out to be very sensitive to changes in the layout (Z-order...) at runtime. It also eats up some space in an otherwise tabular layout, where it should occupy a cell or entire row or column of its own. E.g. a (editor) notebook has no room left for an additional control inside its tabs or editor page.

The next (current) approach uses dockheaders, as already implemented in a docktree or dockbook, to start dragging of the associated client control or notebook page. For that purpose all floating controls (forms...) have to be wrapped into a FloatHostSite, whose DockManager already implements all the functionality of the dockheader. When the dockheader is styled like the grip of a toolbar, this approach fits nicely into any GUI. The DockManager also allows to dock further controls into the same site, eliminating the need for additional ConJoinDockHost sites etc.

Another approach could use dedicated dock-grip controls, as bars with the appearance of commonly used grips (in toolbars...). These tiny bars could be added to a side of any form, just like elastic sites are added, leaving the form layout intact except for a minor reduction of the client width or height. But these controls should be hidden whenever the form is docked, since then the dockheader of the docksite would play the role of the dock-grip. Furthermore such controls would not allow to dock forms together, so that I won't consider this approach right now.

There remain still at least two major issues with wrapping dockable controls into a floating site, the automatic creation of the appropriate FloatHost sites, and the handling of these sites in the store/reload logic of a layout. I already tested the used of the FloatingDockSiteClass property, and found it working in the LCL even with forms, that otherwise float for themselves; perhaps somebody anticipated my idea already?

A minor issue with such floating sites is the visibility of the docked control, when it was just created in invisible state, to reduce flicker. The solution is simple, since it won't do any harm to make the docked client visible in TControl.DoFloatMsg.

Another issue is the exact FloatHost class, that also is used in the Floating property (GetFloating). The meaning or implementation of [Get]Floating had to be changed somehow, when multiple clients can be docked together in the same FloatHost, or in a different FloatHost class. Eventually a dedicated DefaultFloatHostClass can be added to the LCL, that is used with every dockable control (DragKind=dkDock) that has no dedicated FloatingDockSiteClass(=nil).

The currently last issue may deserve more changes to the LCL, related to storing and reloading layouts. Currently persistent layouts are implemented in the DockMaster, which owns all temporary docksites like floating sites, dockbooks and elastic panels. That's why the use of a dedicated DefaultFloatHostClass should be implemented, that is owned by the DockMaster, instead of by Application. At least it should be possible to change the Owner of the floating sites when these are created, but this only would defer the problem, not solve it.

The current implementation of the EasyDockTree and DockMaster try to reduce the dependencies on other (non-LCL) classes, but this is feasable only to some degree. The DockManager with notebook docking capabilities already requires an according notebook component. The DockMaster introduces further classes for floating dock sites and elastic panels, which support the layout streaming, and introduces a layout storage format. All these classes are not required in applications that do not use docking at all, or don't use persistent layouts. But when used, the LCL has to know about some of them.

Minimal impact on the LCL could consist of added global variables, or Application properties, describing the DefaultFloatHostClass and the DockMaster instance (of type TComponent).

A more comfortable implementation would add a TDockMaster or TDesktopLayout base class to the already defined TDockManager class, with methods to stream (and manage?) desktop layouts. The default implementation would do nothing, and an application can install an instance of any derived class. More details will be found out in the practical integration of an DockMaster into the IDE.


Please let me know what you think about my ideas, and what complications I may have missed.