-
Notifications
You must be signed in to change notification settings - Fork 263
Timeline Editor Programming Discussion
A timeline is a graphical representation of a time-sequence. Timelines contain groups that can contain tracks that can contain events, which can be intervals, keys (zero-length intervals) and markers (zero-length events that are on all tracks).
The ATF Timeline Editor Sample is a relatively full-featured timeline editor that allows creating and editing timelines. This sample is built using ATF's timeline facilities. For details, see Timelines in ATF.
This figure shows the TimelineEditor sample:
TimelineEditor shows how to use ATF's timeline facilities, which include interfaces and classes to render, display, and manipulate timelines. General non platform-specific items are in the Sce.Atf.Controls.Timelines
namespace and Atf.Gui
assembly. WinForms classes are in the Sce.Atf.Controls.Timelines.Direct2D
namespace and Atf.Gui.WinForms
assembly. For a discussion of timeline support, see Timelines in ATF.
TimelineEditor uses an XML Schema to define its data model. The "timelineType" is the root type, and such an object can contain "markerType", "timelineRefType", and "groupType" objects. And "groupType" can contain "trackType". A "trackType" contains "intervalType" and "keyType". The base "eventType" has derived types "intervalType", "keyType", and "markerType". Like many other samples, TimelineEditor has a Schema
class of metadata type classes generated by DomGen.
The SchemaLoader
performs the usual tasks of loading the schema and defining DOM adapters for the various types. It also parses annotations, which contain information for palette items and for defining property descriptors, which allow property editors to display object attributes.
TimelineEditor features a set of DOM adapters. DOM adapters defined on the root type have roles as a document adapter, context, or validator. Each timeline object type has a DOM adapter that implements the corresponding interface, described in Timeline Object Interfaces.
There is a TimelineDocument
DOM adapter object for every timeline open in TimelineEditor. Each document has a D2dTimelineControl
canvas control and D2dDefaultTimelineRenderer
to display and render the timeline, as well as a set of manipulators for the timeline.
The TimelineEditor
component creates TimelineDocument
s. It also serves as the control host client for the D2dTimelineControl
and sets up the palette.
The TimelineCommands
component handles commands on the Edit menu to remove a group, track, or empty groups and tracks, as well as to split an interval.
The TimelineContext
DOM adapter derives from Sce.Atf.Dom.EditingContext
, the basic editing context that provides selection and transactions. TimelineContext
implements interfaces to enumerate timeline objects, provide events for timeline object changes, and name timeline objects. It implements IInstancingContext
to create instances of timeline objects when they are copied and pasted or dragged from the palette onto the timeline; this is the where the bulk of the code in TimelineContext
resides because of the intricacy of a timeline.
When a property of a timeline object changes, the TimelineValidator
DOM adapter checks that event attributes are still valid.
Like many of the samples, the data model for TimelineEditor has two types of hierarchy: containment and data type derivation. This is shown in the Visual Studio XML Schema Explorer view of the schema file timeline.xsd
, with the nodes for the container types and base type open:
Containers show how a timeline is built. A "timelineType" contains:
- "markerType" for a marker, a zero-length event on the whole timeline.
- "timelineRefType" for a reference to a timeline.
- "groupType" for a group.
- "trackType" for a track.
- "intervalType" for an event spanning some time on a timeline.
- "keyType" for a zero-length event on a timeline.
- "trackType" for a track.
An "eventType" is an event in a timeline. Rather than using "eventType" directly in a timeline, TimelineEditor uses types derived from "eventType":
- "intervalType"
- "keyType"
- "markerType"
The GenSchemaDef.bat
command file can be used to run DomGen to generate the Schema
class, containing metadata classes for the types specified in timeline.xsd
.
The SchemaLoader
derives from XmlSchemaTypeLoader
and loads the schema file timeline.xsd
.
SchemaLoader
also defines DOM adapters for the sample's data types. To learn about these DOM adapters, see TimelineEditor DOM Adapters.
Finally, SchemaLoader
parses annotations in the XML Schema file. For details on what annotations contain and how they are parsed, see XML Schema Annotations.
Applications can specify property descriptors in two ways:
- Creating them explicitly, typically in the schema loader.
- Specifying them in annotations in the XML Schema.
This example shows the type definition for "intervalType":
<xs:complexType name="intervalType">
<xs:annotation>
<xs:appinfo>
<scea.dom.editors menuText="Interval" description="Interval" image="TimelineEditorSample.Resources.interval.png" category="Timelines" />
<scea.dom.editors.attribute name="name" displayName="Name" description="Name" />
<scea.dom.editors.attribute name="length" displayName="Length" description="Length or Duration" />
<scea.dom.editors.attribute name="color" displayName="Color" description="Display Color"
editor="Sce.Atf.Controls.PropertyEditing.ColorEditor,Atf.Gui"
converter="Sce.Atf.Controls.PropertyEditing.IntColorConverter" />
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="eventType">
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="length" type="xs:float"/>
<xs:attribute name="color" type="xs:integer"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Annotations in this schema specify two things:
- Palette item information.
- Attributes (properties) to display in property editors.
The attributes in the first tag <scea.dom.editors menuText>
provide information for the Interval palette item:
- menuText="Interval": The name of the item displayed in the palette list.
- description="Interval": The tooltip that appears when hovering over the palette item.
- image="TimelineEditorSample.Resources.interval.png": The image file for the Interval icon.
- category="Timelines": The palette in which it appears. This is useful in applications with more than one palette, such as the ATF Diagram Editor Sample.
The subsequent <scea.dom.editors.attribute>
tags define the property descriptors that specify the information needed to display attributes in a property editor. The "intervalType" type has three attributes/properties: Name, Length, and Color. Note that these three properties are also listed in the attributes at the end of the type definition. Each annotation has three attributes: name, displayName, and description. The Color property has two additional attributes. The editor attribute specifies what value type editor is used for the color. The converter attribute tells what converter to use to convert between application data and color values; this converter goes between an ARGB int and a System.Drawing.Color
value. For more information on value editors, see Value Editors and Value Editing Controls.
The ATF DOM Tree Editor Sample also uses annotations for property descriptors. To learn how it handles them, see XML Schema Annotation Property Descriptors in the DOM Tree Editor Programming Discussion.
The type definition for "keyType" illustrates a special property descriptor:
<xs:complexType name="keyType">
<xs:annotation>
<xs:appinfo>
<scea.dom.editors menuText="Key" description="Key" image="TimelineEditorSample.Resources.key.png" category="Timelines" />
<scea.dom.editors.attribute
name="specialEvent"
displayName="Special Event"
description="One or more special events will occur at this time"
editor="Sce.Atf.Controls.PropertyEditing.FlagsUITypeEditor,Atf.Gui.WinForms"
converter="Sce.Atf.Controls.PropertyEditing.ReadOnlyConverter"/>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="eventType">
<xs:attribute name="specialEvent" type="xs:string" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
The "Special Event" property also has an editor defined for it: FlagsUITypeEditor
that handles dynamic flag enumerations to select as many flags as desired in a list box. This editor gets additional support in the annotation parser described in Annotation Parsing. The figure shows this value editor in the Property Editor when a key object is selected in the timeline:
You must enhance the standard schema loader to process annotations so that property descriptors are created. TimelineEditor's schema loader provides several types of special processing.
First, it overrides XmlSchemaTypeLoader.ParseAnnotations()
with its own version, calling the base method. This allows the schema loader to access annotation data and process it.
Second, TimelineEditor's ParseAnnotations()
calls the Sce.Atf.Dom.PropertyDescriptor.ParseXml()
method, which processes the <scea.dom.editors.attribute>
annotation tags that define the property descriptors. The base ParseAnnotations()
method does not call ParseXml()
. Here's the first part of the processing that TimelineEditor's ParseAnnotations()
does:
PropertyDescriptorCollection propertyDescriptors = Sce.Atf.Dom.PropertyDescriptor.ParseXml(nodeType, xmlNodes);
// Customizations
// The flags and enum support from annotation used to be in ATF 2.8.
// Please request this feature from the ATF team if you need it and a ParseXml overload
// can probably be created.
System.ComponentModel.PropertyDescriptor gameFlow = propertyDescriptors["Special Event"];
if (gameFlow != null)
{
FlagsUITypeEditor editor = (FlagsUITypeEditor)gameFlow.GetEditor(typeof(FlagsUITypeEditor));
editor.DefineFlags( new string[] {
"Reward==Give player the reward",
"Trophy==Give player the trophy",
"LevelUp==Level up",
"BossDies==Boss dies",
"PlayerDies==Player dies",
"EndCinematic==End cinematic",
"EndGame==End game",
});
}
nodeType.SetTag<PropertyDescriptorCollection>(propertyDescriptors);
ParseXml()
returns a collection of property descriptors that it created from the annotations in the XML Schema. The variable gameFlow
is set to a descriptor with the name "Special Event" from this collection. Recall that this is a descriptor for an attribute in "keyType", as described in Annotation Information for Key Objects. It obtains the editor corresponding to the property descriptor with this statement:
FlagsUITypeEditor editor = (FlagsUITypeEditor)gameFlow.GetEditor(typeof(FlagsUITypeEditor));
It then calls FlagsUITypeEditor.DefineFlags()
with a string, which defines the flag names and values for the attribute. Calling NamedMetadata.SetTag()
puts this information in the metadata class for "keyType", where it can be accessed for property data. Note that these flags are the same ones shown in the previous illustration of the FlagsUITypeEditor
in the Property Editor for a key. For more information on how property descriptors are set up and used, see Specifying Property Descriptors in the section Property Descriptors.
In the next processing section, the ParseAnnotations()
method looks for the <scea.dom.editors>
tags that specify palette items, as discussed in Palette Information. When it finds these elements, it creates a NodeTypePaletteItem
with that information: the menu text, description, and image:
// parse type annotation to create palette items
XmlNode xmlNode = FindElement(xmlNodes, "scea.dom.editors");
if (xmlNode != null)
{
string menuText = FindAttribute(xmlNode, "menuText");
if (menuText != null) // must have menu text and category
{
string description = FindAttribute(xmlNode, "description");
string image = FindAttribute(xmlNode, "image");
NodeTypePaletteItem item = new NodeTypePaletteItem(nodeType, menuText, description, image);
nodeType.SetTag<NodeTypePaletteItem>(item);
}
}
For details on how the ATF Simple DOM Editor Sample creates and uses a palette, including using NodeTypePaletteItem
, see Using a Palette in Simple DOM Editor Programming Discussion.
TimelineEditor defines three groups of DOM adapters on the sample's data types:
- DOM adapters for the timeline document, defined on the root type, "timelineType":
-
TimelineDocument
: The document DOM adapter. For more details, see Timeline Document and Control. -
TimelineContext
: Context for the timeline document. For more information, see TimelineContext Class. -
MultipleHistoryContext
: DOM adapter for multiple history contexts, so that the undo/redo history stack for each document is distinct.
-
- Validators on timeline data. For this discussion, see Validating Timelines.
- DOM adapters for objects in a timeline. A DOM adapter is defined for each type of object in a timeline. For a description of these adapters, see Timeline Object DOM Adapters.
Most of the DOM adapters are for the types of objects in a timeline, listed in this table:
DOM Adapter | Derives from, Implements | Description |
---|---|---|
BaseEvent
|
DomNodeAdapter , IEvent , ICloneable
|
Base class of adapters for events: intervals, markers, and keys. |
Group
|
DomNodeAdapter , IGroup , ICloneable
|
Adapter for a group of tracks. |
Interval
|
BaseEvent , IInterval
|
Adapter for an interval. |
Key
|
BaseEvent , IKey
|
Adapter for a key. |
Marker
|
BaseEvent , IMarker
|
Adapter for a marker. |
Timeline
|
DomNodeAdapter , IHierarchicalTimeline , IHierarchicalTimelineList
|
Adapter for a timeline. |
TimelineReference
|
DomNodeAdapter , ITimelineReference , ICloneable
|
Adapts a DomNode to a timeline reference, which allows a referenced timeline document to appear within another document, positioned to start at a particular location in that document.
|
Track
|
DomNodeAdapter , ITrack , ICloneable
|
Adapter for a track. |
Some of these adapters implement ICloneable
. Such adapters are for objects than can be moved in TimelineEditor and show as "ghosts" while they are moving. An object is copied to a "ghost", which moves to show the range of movement and destination in TimelineEditor. D2dMoveManipulator
, the manipulator that adds move capabilities to objects, makes a ghost copy using Clone()
:
ICloneable cloneable = ghost.Object as ICloneable;
if (cloneable != null)
ghostCopy = cloneable.Clone() as ITimelineObject;
All the timeline object interfaces derive from ITimelineObject
, so this adaptation works. For more information about manipulators, see WinForms Timeline Renderers, Controls, and Manipulators.
Some of these DOM adapters also override the base ToString()
method for ease of debugging and for tooltip support.
The bulk of the code in these DOM adapters is devoted to implementing the interface of their object types. For instance, Group
implements IGroup
. For details on these various interfaces, see Timeline Object Interfaces.
For example, here is Group
's implementation of IGroup
:
/// <summary>
/// Gets and sets the group name</summary>
public string Name
{
get { return (string)DomNode.GetAttribute(Schema.groupType.nameAttribute); }
set { DomNode.SetAttribute(Schema.groupType.nameAttribute, value); }
}
/// <summary>
/// Gets and sets whether the group is expanded (i.e., are the tracks it contains
/// visible?)</summary>
public bool Expanded
{
get { return (bool)DomNode.GetAttribute(Schema.groupType.expandedAttribute); }
set { DomNode.SetAttribute(Schema.groupType.expandedAttribute, value); }
}
/// <summary>
/// Gets the timeline that contains the group</summary>
public ITimeline Timeline
{
get { return GetParentAs<Timeline>(); }
}
/// <summary>
/// Creates a new track. Try to use TimelineControl.Create(ITrack) if there is a "source" ITrack.
/// Does not add the track to this group.</summary>
/// <returns>New unparented track</returns>
public ITrack CreateTrack()
{
return new DomNode(Schema.trackType.Type).As<ITrack>();
}
/// <summary>
/// Gets the list of all tracks in the group</summary>
public IList<ITrack> Tracks
{
get { return GetChildList<ITrack>(Schema.groupType.trackChild); }
}
Note that the Name
and Expanded
properties simply use the DomNode.GetAttribute()
and DomNode.SetAttribute()
methods to get and set attribute information in the type metadata classes in the Schema
class. These properties provide easy access to the type's attributes.
The Timeline
property uses the DomNodeAdapter.GetParentAs()
method to get the underlying DOM node's parent, adapted as a Timeline
object.
CreateTrack()
creates a new DomNode
of type "trackType", adapted to ITrack
, which Track
implements.
The Tracks
getter uses DomNodeAdapter.GetChildList()
to get the children of the adapted DomNode
as an IList<ITrack>
.
These interactions with the type metadata classes, DomNode
s, and DomNode
and DomNodeAdapter
methods are typical of DOM adapters in this and other ATF samples.
Timeline
implements its object interface ITimeline
in a similar fashion to the other object DOM adapters in this sample.
However, it also implements IHierarchicalTimeline
and IHierarchicalTimelineList
to handle timeline references. Recall that "timelineRefType" objects are contained in an object of type "timelineType". Both IHierarchicalTimeline
and IHierarchicalTimelineList
have a References
property that returns the reference timelines, however they return it differently.
IEnumerable<ITimelineReference> IHierarchicalTimeline.References
{
get { return GetChildList<ITimelineReference>(Schema.timelineType.timelineRefChild); }
}
...
public IList<ITimelineReference> IHierarchicalTimelineList.References
{
get { return GetChildList<ITimelineReference>(Schema.timelineType.timelineRefChild); }
}
Both properties get exactly the same value. However, IHierarchicalTimeline.References
returns it as an IEnumerable<ITimelineReference>
; IHierarchicalTimelineList.References
returns it as an IList<ITimelineReference>
. Users can get either property, depending on what they need.
TimelineEditor's document class is TimelineDocument
. Every timeline document is displayed in its own control canvas, a D2dTimelineControl
that TimelineDocument
creates. D2dTimelineControl
derives from CanvasControl
, a control for viewing and editing a 2D bounded canvas. TimelineEditor can have multiple documents open, with a TimelineDocument
and D2dTimelineControl
for each one.
TimelineDocument
is defined as a DOM adapter for the "timelineType" type, which serves as the timeline document type.
A timeline requires a renderer, canvas control, and manipulators to manipulate timeline objects. For details on these ATF timeline classes, see WinForms Timeline Renderers, Controls, and Manipulators.
TimelineDocument
indirectly implements IDocument
. Here is TimelineDocument
's pedigree:
public class TimelineDocument : DomDocument, ITimelineDocument, IPrintableDocument, IObservableContext
By deriving from DomDocument
, TimelineEditor gets a basic document class. DomDocument
provides a simple and versatile implementation of IDocument
that several samples use for their documents, such as ATF DOM Tree Editor Sample, ATF Fsm Editor Sample, and ATF Simple DOM Editor Sample. In addition, DomDocument
derives from DomResource
, which is a DOM adapter and also implements IResource
to handle URIs for a document's location.
TimelineDocument
also implements ITimelineDocument
, whose Timeline
property gets the ITimeline
object corresponding to the document.
The IPrintableDocument
implementation constructs TimelinePrintDocument
, which derives from CanvasPrintDocument
. This abstract base class is used for canvas printing, and is also used by ATF Circuit Editor Sample, ATF Fsm Editor Sample, and ATF State Chart Editor Sample. TimelinePrintDocument
overrides methods to get "page" bounds for the various PrintRange
values, as well as a method to do the actual rendering to the page to be printed.
The IObservableContext
interface contains events to track items in a context: for items changing, being added or removed, and the collection of items being reloaded. Its implementation has an interesting twist: it subscribes to these interface events in the TimelineContext
class. For example, here's ItemInserted
:
public event EventHandler<ItemInsertedEventArgs<object>> ItemInserted
{
add { this.As<TimelineContext>().ItemInserted += value; }
remove { this.As<TimelineContext>().ItemInserted -= value; }
}
For a discussion of how this context operates, see TimelineContext Class.
A D2dTimelineControl
is constructed by setting TimelineDocument
's Renderer
property, which contains an instance of D2dDefaultTimelineRenderer
, the default timeline renderer:
public TimelineRenderer Renderer
{
get { return m_renderer; }
set
{
if (m_renderer != null)
throw new InvalidOperationException("The timeline renderer can only be set once");
m_renderer = value;
// Due to recursion, we need m_timelineControl to be valid before m_timelineControl.TimelineDocument is set.
// So, we pass in 'null' into TimelineControl's constructor.
m_timelineControl = new TimelineControl(null, m_renderer, new TimelineConstraints(), false);
m_timelineControl.TimelineDocument = this;
m_timelineControl.SetZoomRange(0.1f, 50f, 1f, 100f);
AttachManipulators();
}
}
A D2dDefaultTimelineRenderer
is created when a timeline document is created by TimelineEditor
. For details, see Timeline Document Creation and Editing.
The TimelineControl
(actually a D2dTimelineControl
) is constructed using the given renderer and a TimelineConstraints
object. For further information, see Timeline Constraint Classes.
The last parameter of the D2dTimelineControl
constructor indicates whether to create default manipulators. It's false
here, because the getter calls AttachManipulators()
to explicitly attach manipulators to the D2dTimelineControl
:
protected virtual void AttachManipulators()
{
// The order here determines the order of receiving Paint events and is the reverse
// order of receiving picking events. For example, a custom Control that is drawn
// on top of everything else and that can be clicked on should come last in this
// list so that it is drawn last and is picked first.
D2dSelectionManipulator selectionManipulator = new D2dSelectionManipulator(m_timelineControl);
D2dMoveManipulator moveManipulator = new D2dMoveManipulator(m_timelineControl);
D2dScaleManipulator scaleManipulator = new D2dScaleManipulator(m_timelineControl);
m_splitManipulator = new D2dSplitManipulator(m_timelineControl);
D2dSnapManipulator snapManipulator = new D2dSnapManipulator(m_timelineControl);
D2dScrubberManipulator scrubberManipulator = new ScrubberManipulator(m_timelineControl);
//// Allow the snap manipulator to snap objects to the scrubber.
snapManipulator.Scrubber = scrubberManipulator;
}
Manipulators allow you to manipulate timeline objects in a timeline control, such as moving or scaling timeline objects. The order in which manipulators are attached matters. For a more detailed description of using manipulators, see Timeline Manipulators.
TimelineEditor
is a MEF component imported into TimelineEditor. It serves several roles, including the document client, and implements the following interfaces:
public class TimelineEditor : IDocumentClient, IControlHostClient, IPaletteClient, IInitializable
The implementation of IDocumentClient
is the part that opens and displays timelines. Most of this implentation follows the pattern of other samples, and is described generally in Implementing a Document Client.
The most interesting part of this interface is the Open()
method, because it creates a new window for viewing a document:
public IDocument Open(Uri uri)
{
TimelineDocument document = LoadOrCreateDocument(uri, true); //true: this is a master document
if (document != null)
{
m_controlHostService.RegisterControl(
document.TimelineControl,
document.Cast<TimelineContext>().ControlInfo,
this);
document.TimelineControl.Frame();
}
return document;
}
LoadOrCreateDocument()
creates the document. The D2dTimelineControl
of the new TimelineDocument
is registered with the Control Host Service.
The LoadOrCreateDocument()
method shows a specific example of creating a timeline renderer and control.
The initial phase of LoadOrCreateDocument()
accomplishes the routine tasks of reading data from a given URI for an existing document into a DomNode
using a DomXmlReader
or creating a new DomNode
for the root node of a new document. This is followed by the heart of the method: these statements result in the creation of the key objects needed for displaying a timeline:
D2dTimelineRenderer renderer = CreateTimelineRenderer();
document.Renderer = renderer;
renderer.Init(document.TimelineControl.D2dGraphics);
CreateTimelineRenderer()
is a trivial method that simply constructs a D2dDefaultTimelineRenderer
:
protected virtual D2dTimelineRenderer CreateTimelineRenderer()
{
return new D2dDefaultTimelineRenderer();
}
For further information, see Timeline Renderers.
The next line sets the TimelineDocument.Renderer
property. This results in creating the D2dTimelineControl
and attaching manipulators to it, as discussed in Timeline Control Creation.
The last line calls D2dDefaultTimelineRenderer.Init()
, which calls its base D2dTimelineRenderer.Init()
. These Init()
methods set up the various Direct2D graphics objects used to render a timeline and its objects.
After these essential timeline objects are created and set up, a few more tasks remain in creating the document. The timeline context is handled next:
string fileName = Path.GetFileName(filePath);
ControlInfo controlInfo = new ControlInfo(fileName, filePath, StandardControlGroup.Center);
//Set IsDocument to true to prevent exception in command service if two files with the
// same name, but in different directories, are opened.
controlInfo.IsDocument = true;
TimelineContext timelineContext = document.Cast<TimelineContext>();
timelineContext.ControlInfo = controlInfo;
A ControlInfo
object is constructed. The TimelineDocument
is adapted to a TimelineContext
, which works because TimelineContext
is a DOM adapter defined on "timelineType", just like TimelineDocument
. The context's ControlInfo
property is set to the new ControlInfo
.
Timeline documents may contain timelines, which is the reason for the "timelineRefType". The next step is to open any of these sub-timeline documents:
IHierarchicalTimeline hierarchical = document.Timeline as IHierarchicalTimeline;
if (hierarchical != null)
{
ResolveAll(hierarchical, new HashSet<IHierarchicalTimeline>());
}
ResolveAll()
does a depth-first traversal, resolving all referenced sub-documents. If any are found, it calls LoadOrCreateDocument()
recursively, so that all sub-timelines are found, not matter how deeply embedded they are.
TimelineEditor
is the control host client for the D2dTimelineControl
. The IControlHostClient
implementation is routine. For a description of what control clients do, see IControlHostClient interface.
TimelineEditor provides a palette of timeline objects, and it implements this palette similarly to other samples. IPaletteClient
, in particular, provides methods to get information on palette items and to convert a palette item to an instance that can be inserted into the timeline. Palette information is provided in the XML Schema, as discussed in Annotation Information for Interval Objects and Annotation Parsing.
TimelineEditor also imports the PaletteService
component to manage the palette. In its constructor, it uses the imported palette service to add items to the palette:
paletteService.AddItem(Schema.markerType.Type, "Timelines", this);
paletteService.AddItem(Schema.groupType.Type, "Timelines", this);
paletteService.AddItem(Schema.trackType.Type, "Timelines", this);
paletteService.AddItem(Schema.intervalType.Type, "Timelines", this);
paletteService.AddItem(Schema.keyType.Type, "Timelines", this);
paletteService.AddItem(Schema.timelineRefType.Type, "Timelines", this);
TimelineEditor's palette implementation is very similar to the one in ATF Simple DOM Editor Sample. For a description of using a palette in that sample, see Using a Palette.
The TimelineEditor
component completes its initialization in its IInitializable.Initialize()
method. Initialize()
uses the ScriptingService
and SettingsService
, which may have not been initialized at the time TimelineEditor
's constructor runs.
Initialize()
sets up information with ScriptingService
that is useful for debugging.
Initialize()
creates a BoundPropertyDescriptor
array with information that is to persist between sessions using the SettingsService
. For further details on using the setting service to save preference information, see SettingsService Component.
TimelineCommands
is the component that handles commands on the Edit menu.
Its ICommandClient
implementation is the command client for commands to remove a group, track, or empty groups and tracks, as well as to split an interval.
The ICommandClient.DoCommand()
method does these commands using the properties of the various objects. For example, here's how the command to remove a group is performed:
ITimelineObject target = ContextRegistries.GetCommandTarget<ITimelineObject>(m_contextRegistry);
if (target == null)
return false;
IInterval activeInterval = target as IInterval;
ITrack activeTrack =
(activeInterval != null) ? activeInterval.Track : Adapters.As<ITrack>(target);
IGroup activeGroup =
(activeTrack != null) ? activeTrack.Group : Adapters.As<IGroup>(target);
ITimeline activeTimeline =
(activeGroup != null) ? activeGroup.Timeline : Adapters.As<ITimeline>(target);
ITransactionContext transactionContext = context.TimelineControl.TransactionContext;
switch ((Command)commandTag)
{
case Command.RemoveGroup:
if (activeGroup == null)
return false;
if (doing)
{
transactionContext.DoTransaction(delegate
{
activeGroup.Timeline.Groups.Remove(activeGroup);
},
"Remove Group");
}
return true;
...
First, the command's target is obtained from the context registry's GetCommandTarget()
method, which is the last selected ITimelineObject
. The method then attempts to adapt the selected object to various timeline object interfaces, described in Timeline Object Interfaces. An ITransactionContext
object is obtained from the D2dTimelineControl
to use with commands so they go into the history stack and are undoable.
The group is removed in a delegate inside the TransactionContexts.DoTransaction()
method. The active group is removed from the list of groups in the timeline held in the ITimeline.Groups
property.
For a description of how command clients work in general, see Creating Command Clients.
TimelineContext
is a DOM adapter defined for "timelineType", the root type. Here is its definition:
public class TimelineContext :
EditingContext,
IEnumerableContext,
IObservableContext,
INamingContext,
IInstancingContext
TimelineContext
derives from Sce.Atf.Dom.EditingContext
, which is a general purpose editing context used as in several samples. EditingContext
implements ISelectionContext
and ITransactionContext
, so it provides for selection and undo/redo of editing operations. In particular, EditingContext
defines a Selection
property that is a AdaptableSelection<object>
; using this object makes it easy to implement ISelectionContext
. For more information about this context's capabilities, see Sce.Atf.Dom.EditingContext Class.
The EditingContext
that TimelineContext
derives from is a DOM adapter, so TimelineContext
has an OnNodeSet()
method, called when the DomNode
is initialized. Because TimelineContext
is defined on the root type, the DomNode
is the root of the DomNode
tree. This method is called when a timeline is created, as when a timeline document is opened.
protected override void OnNodeSet()
{
m_timeline = this.As<Timeline>();
m_timelineDocument = DomNode.Cast<TimelineDocument>();
m_timelineControl = m_timelineDocument.TimelineControl;
DomNode.AttributeChanged += DomNode_AttributeChanged;
DomNode.ChildInserted += DomNode_ChildInserted;
DomNode.ChildRemoved += DomNode_ChildRemoved;
m_timelineControl.DragEnter += timelineControl_DragEnter;
m_timelineControl.DragOver += timelineControl_DragOver;
m_timelineControl.DragDrop += timelineControl_DragDrop;
m_timelineControl.DragLeave += timelineControl_DragLeave;
base.OnNodeSet();
}
This DomNode
can be adapted to Timeline
and TimelineDocument
, because these DOM adapters are all defined on the root type.
The DomNode
events subscribed to occur whenever any DomNode
in the tree changes or is inserted or removed anywhere in the tree, since they are subscribed to on the root DomNode
. The handlers for these events, in turn, call methods that raise events in IObservableContext
.
Drag events for the D2dTimelineControl
in m_timelineControl
allow handling dragging objects from the palette to the timeline. The insertion of these dragged items into the timeline is handled by the IInstancingContext
interface. For details, see Drag and Drop Handling and also IInstancingContext Interface.
TimelineContext
implements interfaces that many other editing contexts in the samples also implement:
-
IEnumerableContext
: Enumerate objects in the timeline. -
IObservableContext
: Events for timeline objects being added or removed. This is required so the timeline can be redrawn if it changes. -
INamingContext
: Get or change the name of timeline objects. -
IInstancingContext
: Handle copy and paste of selected timeline objects, creating new instances.
IInstancingContext
. For details on that interface's operations, see IInstancingContext Interface.
INamingContext
is an interface for contexts where objects can be named. Most timeline objects have a Name
property, which this interface's implementation takes advantage of.
To demonstrate this simple interface, first consider its GetName()
method, which gets the item's name in the context, or null
if none:
string INamingContext.GetName(object item)
{
IEvent e = item.As<IEvent>();
if (e != null)
return e.Name;
IGroup group = item.As<IGroup>();
if (group != null)
return group.Name;
ITrack track = item.As<ITrack>();
if (track != null)
return track.Name;
return null;
}
GetName()
first tries to adapt the item to IEvent
. If that works, it can get the name from the IEvent.Name
property. This works whether the item is an IInterval
or IMarker
, because these both derive from IEvent
. Failing that, it tries to adapt the item to IGroup
, and if that works, it returns the IGroup.Name
property. Finally, it tries to adapt to ITrack
, the last nameable timeline object, returning the ITrack.Name
property if successful.
Next, look at the beginning of CanSetName()
, which returns whether the item can be named:
bool INamingContext.CanSetName(object item)
{
IEvent e = item.As<IEvent>();
if (e is IKey)
return false;//Keys.Name currently is always the empty string
if (e != null)
return IsEditable(e);
...
}
Again, the method attempts adaptation to IEvent
. If the item is an IKey
— which is based on IEvent
— the method returns false, because keys are not named, unlike the other events. If it's another kind of IEvent
, the method returns whatever TimelineContext.IsEditable()
does:
private bool IsEditable(ITimelineObject item)
{
var path = new TimelinePath(item);
TimelineDocument document = (TimelineDocument)TimelineEditor.TimelineDocumentRegistry.ActiveDocument;
if (document != null)
return document.TimelineControl.IsEditable(path);
return false;
}
IsEditable()
constructs a TimelinePath
for the item. It gets the active TimelineDocument
from the document registry. It uses the document's TimelineControl
property to get the D2dTimelineControl
. It can then call this control's IsEditable()
method, which determines whether the object corresponding to the given path is editable.
Finally, here is SetName()
that sets the item's name:
void INamingContext.SetName(object item, string name)
{
IEvent e = item.As<IEvent>();
if (e != null)
{
e.Name = name;
return;
}
...
}
SetName()
acts like the other methods, trying to adapt the item to the various nameable objects, and setting the value of the Name
property for the object when adaptation succeeds.
This interface handles creating new instances of timeline objects when they are copied or cut and then pasted to the timeline, or are dragged from the palette and dropped on the timeline. For a general description of IInstancingContext
, see IInstancingContext Interface. For a full discussion of instancing in general, see Instancing In ATF.
Note that instancing operations go on the history stack, because TimelineContext
implements transactions through its base class, Sce.Atf.Dom.EditingContext
.
There are several ways in which objects may be instanced:
- Copy a timeline object and paste it.
- Drag an object from the palette onto the timeline.
D2dMoveManipulator
. For more information on manipulators, see Timeline Manipulators.
The implementation of IInstancingContext
's methods is very timeline specific but fairly simple — except for Insert()
. For example, here is CanInsert()
:
public bool CanInsert(object insertingObject)
{
IDataObject dataObject = (IDataObject)insertingObject;
object[] items = dataObject.GetData(typeof(object[])) as object[];
return
items != null &&
AreTimelineItems(items) &&
(TimelineControl.TargetGroup == null ||
TimelineControl.IsEditable(TimelineControl.TargetGroup));
}
The first couple of statements get the data to be inserted and convert it to an array of object
s. The following must all be true to allow insertion:
- Inserted data is not
null
. - Inserted data consists only of
ITimeline
objects, as determined byAreTimelineItems()
. - Either the selected target group (
TimelineControl.TargetGroup
) isnull
or it is editable.
Insert()
is called when a paste command occurs and is considerably more complicated than the other IInstancingContext
methods. A timeline has a hierarchy of objects, so the target insertion location must be suitable for the type of inserted objects. Intervals are pasted onto tracks, for example, and tracks are pasted onto groups. There is a great deal of special case processing in Insert()
.
Insert()
begins by verifying there is data to copy and turning it into an array of object
s. It makes a copy of the objects to paste as an array of DomNode
s. It makes a List<ITimelineObject>
from this DomNode
array. So far, this is fairly standard for Insert()
implementations.
It tries to determine where objects should be copied by further processing, such as finding the center position of the copied objects, making a dictionary mapping objects to their tracks, and so on.
Insert()
now guesses where the user wants to paste, based on this priority, figuring which of these is available as a target:
- The timeline control's target (currently selected) track.
- The track in the center of the view.
- The first visible track.
Insert()
method:
foreach (ITimelineObject item in itemCopies)
{
// Not all items will have a track. The item could be a group, for example.
ITrack dropTarget;
copiesToTracks.TryGetValue(item, out dropTarget);
Insert(item, dropTarget);
}
The variable copiesToTracks
is a dictionary mapping target timeline objects to the tracks they should be placed in. The private Insert()
method looks at the various types of timeline objects involved in the insertion and determines exactly where the inserted item should go and places it there. As the comment indicates, not all objects are inserted into a track.
Finally, Insert()
selects the items that were just inserted into the timeline.
Another public Insert()
method handles inserting items by drag and drop, and this also calls the private Insert()
method to do the actual insertion.
About half of TimelineContext
consists of utility methods used by Insert()
and the other IInstancingContext
methods. For example, CreateTrackMappings()
determines which tracks to place timeline objects in. CenterEvents()
finds the center point of a set of timeline objects.
As noted previously in OnNodeSet Method, handlers for the drag and drop events on the D2dTimelineControl
call methods in IInstancingContext
. For example, this is the handler for the DragDrop
event:
private void timelineControl_DragDrop(object sender, DragEventArgs e)
{
if (CanInsert(e.Data))
{
OnDragDrop(e);
}
}
This calls IInstancingContext.CanInsert
, which was discussed in IInstancingContext Non Insert Methods. If the insertion is OK, it calls OnDragDrop()
to drop the item onto the timeline:
protected virtual void OnDragDrop(DragEventArgs e)
{
string name = "Drag and Drop".Localize();
Selection.Clear();
this.DoTransaction(() => Insert(e), name);
m_timelineControl.DragDropObjects = null;
m_timelineControl.Focus();
}
OnDragDrop()
calls the previously mentioned private Insert()
method in a transaction to insert the dragged item. It then nullifies the D2dTimelineControl
's drag-drop list and gives focus to the D2dTimelineControl
.
In SchemaLoader
, TimelineEditor defines three validators on the root type "timelineType":
Schema.timelineType.Type.Define(new ExtensionInfo<UniqueIdValidator>());
Schema.timelineType.Type.Define(new ExtensionInfo<ReferenceValidator>());
Schema.timelineType.Type.Define(new ExtensionInfo<TimelineValidator>());
UniqueIdValidator
and ReferenceValidator
make sure that DomNode
IDs are unique and all references to these IDs are valid. These validators are not really needed in TimelineEditor, because there are no ID attributes or references to them. Many samples use these validators though, such as ATF Fsm Editor Sample and ATF State Chart Editor Sample. Though they are not used, defining the validators does no harm, except for a slight amount of unnecessary checking for DomNode
IDs.
TimelineValidator
checks that timeline event attributes are valid. It overrides the OnAttributeChanged()
method in Observer
, which is called whenever a DomNode
attribute changes. In other words, OnAttributeChanged()
is called whenever a attribute/property of a timeline object changes.
Because these validators are defined on the type of the root DomNode
, all nodes in the DomNode
tree are checked, that is, all application data is checked.
Here's OnAttributeChanged()
:
protected override void OnAttributeChanged(object sender, AttributeEventArgs e)
{
BaseEvent _event = e.DomNode.As<BaseEvent>();
if (_event != null)
{
if (e.AttributeInfo.Equivalent(Schema.eventType.startAttribute))
{
float value = (float)e.NewValue;
float constrained = Math.Max(value, 0); // >= 0
constrained = (float)MathUtil.Snap(constrained, 1.0); // snapped to nearest integral frame number
if (constrained != value)
throw new InvalidTransactionException(Localizer.Localize("Timeline events must have a positive integer start time"));
return;
}
Interval interval = _event.As<Interval>();
if (interval != null)
{
if (e.AttributeInfo.Equivalent(Schema.intervalType.lengthAttribute))
{
float value = (float)e.NewValue;
float constrained = Math.Max(value, 1); // >= 1
constrained = (float)MathUtil.Snap(constrained, 1.0); // snapped to nearest integral frame number
if (constrained != value)
throw new InvalidTransactionException(Localizer.Localize("Timeline intervals must have an integer length"));
return;
}
}
}
}
Only event timeline objects are checked:
- Timeline events must have a positive integer start time.
- Timeline intervals must have an integer length; an interval is a type of event.
AttributeEventArgs.Node
is the DomNode
whose attribute has changed or will change. It is adapted to BaseEvent
, the DOM adapter base class for events. If the adaptation is unsuccessful, the changed DomNode
does not correspond to an event and does not need to be checked.
AttributeEventArgs.AttributeInfo
is the AttributeInfo
for the changed attribute of the DomNode
. The method checks that this corresponds to the attribute for the start time of the event. If so, the new start time value is obtained from AttributeEventArgs.NewValue
and checked that it is greater than or equal to zero. If not, it raises an exception.
If the changed attribute is not a start time, the validator attempts to adapt the DomNode
to Interval
. If successful, it checks whether the changed attribute is for the interval length. In that case, it checks that the new interval length is an integer and raises an exception if it isn't.
- Circuit Editor Programming Discussion: Learn how ATF handles graphs, and provides editors for kinds of graphs, such as circuits.
- Code Editor Programming Discussion: Shows how to interface third party software to an ATF application: the ActiproSoftware SyntaxEditor.
- Diagram Editor Programming Discussion: Very simply combines components from the CircuitEditor, FsmEditor, and StateChartEditor samples into one application, with the abilities of all three, showing the power of components.
-
DOM Property Editor Programming Discussion: Shows how to use the ATF DOM with an XML Schema to define application data with a large variety of attribute types, whose values can be viewed and edited using the ATF
PropertyEditor
component, using various value editors to view and edit attributes. - DOM Tree Editor Programming Discussion: Shows how to edit DOM data using a tree control and display properties in a variety of value editors.
- File Explorer Programming Discussion: Discusses the ATF File Explorer Sample using list and tree controls with adapters.
- FSM Editor Programming Discussion: Tells you about how the ATF FSM Editor Sample edits simple graphs for state machines, using DOM adapters for contexts and validation.
-
Model Viewer Programming Discussion: Shows how the ATF Model Viewer Sample is written, discussing how ATGI and Collada model data is handled, using rendering components, and using a
DesignControl
as a canvas for rendering. -
Simple DOM Editor Programming Discussion: Programming the ATF Simple DOM Editor Sample, creating a palette, using DOM adapters and contexts, editing application data, and searching
DomNode
s. - Simple DOM Editor WPF Programming Discussion: Programming the ATF Simple DOM Editor WPF Sample, which is similar to ATF Simple DOM Editor Sample, but implemented using ATF's WPF framework.
- Simple DOM No XML Editor Programming Discussion: Programming the ATF Simple DOM No XML Editor Sample, which is very similar to ATF Simple DOM Editor Sample, except that it doesn't use XML for either its data model or persisting application data.
- State Chart Editor Programming Discussion: Shows using ATF graph and other classes to create a statechart editor, using DOM adapters, documents, contexts, and validators.
- Target Manager Programming Discussion: Description of how a target manager is implemented using ATF components to manage target devices, such as PlayStation®Vita or PS3™ consoles. A target manager is used in other tools, such as the StateMachine tool.
- Timeline Editor Programming Discussion: Discusses how to create a fairly full-featured timeline editor using the ATF timeline facilities, such as the timeline renderer and the timeline control and its manipulators.
-
Tree List Control Programming Discussion: Demonstrates using the
TreeListControl
andTreeListItemRenderer
classes to display and edit hierarchical data in a tree view with details in columns. -
Tree List Editor Programming Discussion: Demonstrates how to use the ATF tree controls
TreeListView
and its enhancement,TreeListViewEditor
.TreeListView
usesTreeListViewAdapter
, which adaptsTreeListView
to display data in a tree. - Using Dom Programming Discussion: Shows how to use the various parts of the ATF DOM: an XML Schema, a schema metadata class file generated by DomGen, DOM adapters for the data types, a schema loader, and saving application data to an XML file.
- Home
- Getting Started
- Features & Benefits
- Requirements & Dependencies
- Gallery
- Technology & Samples
- Adoption
- News
- Release Notes
- ATF Community
- Searching Documentation
- Using Documentation
- Videos
- Tutorials
- How To
- Programmer's Guide
- Reference
- Code Samples
- Documentation Files
© 2014-2015, Sony Computer Entertainment America LLC