Skip to content

Commit

Permalink
second tutorial snapshot save
Browse files Browse the repository at this point in the history
  • Loading branch information
megaannum committed Aug 23, 2012
1 parent ceb6a35 commit ee8c778
Show file tree
Hide file tree
Showing 7 changed files with 915 additions and 6 deletions.
2 changes: 1 addition & 1 deletion doc/forms.txt
Expand Up @@ -448,7 +448,7 @@ inheritance framework ({Self.vim} is the {Forms} library's sole dependency).


At the base of the Glyph inheritance tree is the Glyph (forms#Glyph) which At the base of the Glyph inheritance tree is the Glyph (forms#Glyph) which
has the Self Object Prototype as its prototype. The Glyph prototype has has the Self Object Prototype as its prototype. The Glyph prototype has
method that define the behavior of all derived Glyph types as well as methods that define the behavior of all derived Glyph types as well as
inheriting the Self Object's methods. When the term {Glyph} is used, much inheriting the Self Object's methods. When the term {Glyph} is used, much
of the time it refers to an object that is based upon the Glyph Prototype. of the time it refers to an object that is based upon the Glyph Prototype.


Expand Down
190 changes: 190 additions & 0 deletions tutorial/forms/CreatingGlyph.md
@@ -0,0 +1,190 @@
# Creating a new Glyph

## Minimum Methods

Since there already exists the 'null' Glyph (g:forms#NullGlyph)
that has no size and draws nothing, any Glyph a developer might
define needs to have both a size and renders something.

* requestedSize()
This method returns the size, List containing width and height, that
the Glyph would like to occupy when drawn.

* draw(allocation)
Draw the Glyph at the location and size given by the Dictionary 'allocation'.
The allocation contains the key/values for 'line', 'column', 'width',
and 'height'. A 'draw()' method generally follows this template:

function SomeGlyph.draw(allocation) dict
let self.__allocation = a:allocation
if self.__status != g:IS_INVISIBLE
" Code to draw Glyph
endif

" For interactive Glyphs
if self.__status == g:IS_DISABLED
" Code to 'color' the Glyph as "disabled"
" A "disabled" is one that can not accept focus
call AugmentGlyphHilight(self, "DisableHi", a)
endif
endfunction

It is *critically* important when rendering a Glyph, that the Forms library
can and does use multi-byte characters, UTF-8; that some character
drawing commands will screw things up. It is best to rely on the
existing Character and String drawing functions:

forms#SetCharAt(chr, line, column)
forms#SetHCharsAt(chr, nos, line, column)
forms#SetVCharsAt(chr, nos, line, column)
forms#SetStringAt(str, line, column)
forms#SubString(str, start, ...)

See the bottom of the forms.vim code for usage.

## Has Attributes

If the new Glyph has any additional attributes, then it is also important
to define the methods:

* init(attrs)
While the Self Prototype 'init()' method will match attributes with their
intended values in the attrs Dictionary, a Glyph might wish to define its
own 'init()', which, of course, would call its Prototype's 'init()',
in order to check the values of the attributes have been set to and
make sure that all attributes that need to be set are set.

function SomeGlyph.init(attrs) dict
" call Prototype init method
call call(self.__prototype.init, [a:attrs], self)
" check attribute values, throw exception if bad
...
return self
endfunction

Very important, remember that the 'init()' method returns 'self'.

* reinit(attrs)
A Glyph's 'reinit()' method is called if one wishes to change one or more
attribute values. This is a safe but expensive way of changing a Glyph's
attributes. This approach should be used is some fundamental aspect of a
Glyph, some attribute that one expects to be constant, needs to be changed.
Consider the Label's 'reinit()' method which changes the Label's text:

function! FORMS_LABEL_reinit(attrs) dict
let oldText = self.__text
let self.__text = ''

call call(g:forms#Leaf.reinit, [a:attrs], self)

if oldText != self.__text
call forms#PrependUniqueInput({'type': 'ReSize'})
else
call forms#ViewerRedrawListAdd(self)
endif
endfunction

Generally, the text associated with a Label is expected not to change.
In the above, the old value of the text is saved and then the Label's
'init()' method is called. Upon its return, there are two possiblities:

- The size (number of characters) in the new text is different from
the size of the old text. In this case, the size of the Label has changed.
This requires that the whole Form be re-size, an expensive operation.
This is triggered by adding a 'ReSize' Event to the Input Queue. The
'ReSize' Event is *not* handled by the Viewer's event loop but rather
the Form's event loop. The Form must restore the existing text, figure
out how big the Form with the new Label text must be, save the existing
text, and then re-enter the Viewer event loop redrawing the whole Form in
the process.

- The second possibility is that the size of the old and new text is the
same, so all that is required is for the Label to be redraw. This happens
by adding the Label to the Viewer's Redraw List. After the Viewer
event loop handles every Event, it checks its Redraw List to see if any
Glyph or Glyphs need to be redrawn and, if so, those Glyphs and no other
Glyphs are redrawn.

## Interactive

To go further, the developer must decide if the new Glyph is to be interactive
or not and whether it will have children or not.


If the new Glyph is interactive, it can get and lose (a Viewer's Event
handling) focus; it has a 'hotspot' and may display a 'flash' if
cursor movement within it exceed its boundaries; it probably will
have user entered data which can be added to the 'results' Dictionary
when the Form is exiting due to a 'Submit' Event; and it handles both
characters and Events. So, an interactive Glyph needs to define the methods:

* canFocus()
A Glyph can be enabled, disabled or invisible. If the Glyph is enabled, then
and only then can it have focus, so the 'canFocus()' method should look like:

function SomeGlyph.canFocus() dict
return (self.__status == g:IS_ENABLED)
endfunction

* gainFocus()
Sometimes a Glyph needs to take some action when it first gains focus.
This method is used to tell the Glyph it has gained focus.

* loseFocus()
Notify the Glyph that it has lost focus. This needs to be defined only if
the Glyph will take some action upon losing focus.

* hotspot()
Draw the hotspot associated with the Glyph.

* flash()
This method is called if there has been some input error or cursor motion
error.

* addResults(resutls):
An interactive Glyph generally has user entered data. This method is called
while the Form is handling an 'Submit' Event to enter the Glyph's data into
the 'results' Dictionary;

function! SomeGlyph(results) dict
let tag = self.getTag()
let a:results[tag] = self.__some_data
endfunction

If the Glyph has multiple chunks of data, the value entered into the
'results' should be a ordered List of those chunks.

* handleChar(nr)
Handles the Number returned from 'getchar()' when Glyph has focus.

* handleEvent(event)
Handles the Event returned from Input Queue when Glyph has focus.

In many cases, the result of handling a Character or Event is for the
Glyph to add itself to the Viewers Redraw List. On some occasions, the
Glyph will call its 'flash()' method when the entered data is invalid
or results in a bad cursor (bad hotspot) position.

In addition, the developer might wish to add context specific usage information
to the Glyph which allows user to quick see how to use the Glyph, thus
the 'usage()' method should be defined:

* usage()
A Generic description of how a user interacts with the Glyph possible
including what keyboard and mouse events are handled.

## Non=Interactive

When a Glyph is non-interactive, none of the above methods needs to be
defined.

## Number of Children

Any new Glyph must have as a Prototype ancestor either: Leaf, Mono, Poly
or Grid; one should *never* derive directly from the Glyph Prototype.
When deriving from Mono, Poly or Grid, many methods must be structured
so that they call the Glyph's Prototype's corresponding method (unless
one wishes to duplicate or replace all of the code).



65 changes: 65 additions & 0 deletions tutorial/forms/Events.md
@@ -0,0 +1,65 @@
# Events

There are two types of input events. The first type is simply the
Character or Number returned by calling Vim's {getchar()} function.
These are called "character" events. A {Viewer} examine all such
character events and map some of them into the second type of event
supported by {Forms}, a Dictionary object or object event that must
have a 'type' key whose value is one of a set of allowed names. As an
example, a <LeftMouse> Number returned by {getchar()} is converted
into a Dictionary event object of type 'NewFocus' where the Dictionary
also has entries for the line and column (v:mouse_line and
v:mouse_column). Some object events are generated by the {Forms}
runtime system. An example of such an object event is a ReSize event
which is generated when a Glyph actually changes its size (normally, a
very rare occurrence). More common runtime generated event objects are
the Cancel and Submit events which are, generally, generated by a
"close" and an "accept" button.

A user interacts with a form by sending events to the form. Such
events are generated by the keyboard and mouse. Events are
read by the {Viewer} with current focus, mapped by that {Viewer}
and then forwarded to the Viewer's child Glyph that has focus.
If a {Viewer} has no child Glyphs that accept focus, then all events
are ignored (except the <Esc> character which will pop the user out
of the current Viewer/Form).

If a Glyph consumes an input event, it might require redrawing (such
as adding a character to an editor). In this case, the Glyph registers
itself with a viewer-redraw-list and when control is returned to the
{Viewer}, the Glyph is redrawn.

Sometimes a Glyph will consume an event and an action will be
triggered. For example, a left mouse click or keyboard <CR> entry
on a button will cause the action associated with that button to
execute.

## Limitations

### New Types of Events

A developer can create a new Event type, but the Glyphs that handle Events
will simply ignore it.

Currently, any Glyph that handles Events does so in one of its methods;
there is no notion of a pluggable, independent Event Handler.

### Representation

I've thought about but never reached a design that I was happy with
concerning the scripting of a Form interaction. That is, add Events
to the Input Queue and then call a Form's 'run()' method.
The issue is that of representing the Number and String that are
returned by 'getchar()' and 'nr2char()' methods. And, in addition,
some events have secondary data such as Mouse Events.

A companion problem is how to log and capture a user's 'getchar()' Character
stream in some format that would allow for editing the resultant
captured data and replay.

In both cases, I do not see a solution short of enumerating all possible
Character/Event types.

I might add, a good Character/Event capture/edit/replay capability would
allow for creating a suit of non-visual unit tests for the Forms library.

0 comments on commit ee8c778

Please sign in to comment.