-
Notifications
You must be signed in to change notification settings - Fork 1
Proxima Client
The Proxima JavaScript client (source: $proxima/scripts/Editor.xml
) keeps track of a HTML rendering tree that represents the rendering of the edited document. The script catches all edit events that occur in the browser (mouse and keyboard) and sends these as commands to the server. The incremental updates that are received from the server are applied to the HTML rendering tree.
The function sendCommand
sends a command to the server. The syntax of these commands consists of the showed elements of the Command
datatype declared in $proxima/src/Proxima/GUIServer.hs
:
data Command = Metrics ((String,Int,Bool,Bool),(Int,Int,[Int]))
| ContextMenuRequest ((Int,Int),(Int,Int))
| ContextMenuSelect Int
| Key (Int,Modifiers)
| Chr (Int,Modifiers)
| Mouse MouseCommand (Int,Int, Modifiers)
| EditStyle StyleEdit
| Find String
| SetViewedArea CommonTypes.Rectangle
| Redraw
| Rearrange
| ClearMetrics
deriving (Show, Read)
data MouseCommand = MouseDown | MouseMove | MouseUp | MouseDragStart | MouseDrop
deriving (Show, Read)
type Modifiers = (Bool,Bool,Bool)
Because edit events may occur in quick succession, the client maintains a queue for edit events (global variable commandQueue
). When the client is waiting for a server response, any additional events are queued until the result has arrived, after which all queued commands are sent. Communication with the server is performed through an XMLHTTP object, and on a server response, function state_Change
updates the status lights and processes the received updates by calling updateTree
for each update.
For each font in the presentation, the server requires the character height, the baseline and a list of character widths. Unfortunately, JavaScript does not support general font queries. To solve the problem, initFont
creates an invisible DIV element with id="font"
that contains all characters of the font. The height and widths can be measured from this font
element. (Each character is replicated a 1000 times to increase accuracy.) In order to determine the baseline, a single letter is rendered next to an element with height 0. Because the element is aligned at the baseline of the letter, its relative position (which can be queried) is the value for the baseline.
On a font query, the function queryFont
applies the queried font and style to the font
element and returns the collected metrics, which are sent to the server by function updateTree
as a Metrics
command.
Because there is no default support for context menus in JavaScript, we use code from Dynamic Drive (www.dynamicdrive.com). On a right click mouse event, a ContextMenuRequest
command with the mouse position and the coordinates of the top-left corner of the screen are sent to the server. The server responds with a list of items that are displayed as a context menu, and when an item is clicked, a ContextMenuSelect
command is sent with the index of the clicked menu item.
The mouse handler (mouseHandler
) sends events to the server based on user mouse events. In addition, it handles scrolling and drag and drop. Standard JavaScript croll events cannot be used, because these are also generated when the rendering is scrolled programmatically. Using a global variable to signal this difference did not turn out reliable. Therefore, scroll events are generated explicitly, when the user drags inside a scroll bar. A downside of this method is that it doesn't support scroll wheels.
The mouse handler implements a small finite state machine to handle drag and drop. Its states are:
0: No button down
1: Button down, but no drag yet
2: Drag in progress
3: Dragging on a non-dragsource, for example when the user extends the selection
The state transitions are straightforward and can be seen in the code. Drag sessions are only started when the element under the cursor has class Draggable
. In order to provide an image of the dragged node, a clone is stored in draggedElementClone
.
Network latency may cause a delay between sending the edit event and receiving the update from the server. We tackle this problem by having the client show the predicted effect of an edit operation, until the actual update is obtained from the server.
When a key is entered, insertCharPredictively
inserts the corresponding character in the HTML rendering tree at the position of the cursor, and the cursor is moved to the right by the width of the character. Furthermore, all right sibling elements in the same row and in parent rows are moved to the right as well, until a parent column is encountered.
Because edit operations are queued if the client is already waiting for a response, we need to keep track of two kinds of predictively inserted characters: characters corresponding to the already sent insert operations (sentPredictiveInserts
), and characters corresponding to the currently queued insert operations (currentPredictiveInserts
). On arrival of the server response, all predictive inserts are undone with removeAllPredictiveInserts
, to make it possible to apply the received incremental update on the rendering. Subsequently, the queued inserts are sent to the server, and the corresponding predictive updates are performed on the rendering again (by reApplySentPredictiveInserts
).
Because a presentation in Proxima can be complex, it is not always possible to correctly predict the effect of an inserted character. Nevertheless, the algorithm works well for all editors in the quite diverse suite of sample editors, and it provides a massive increase in response time. An area of future research will be to enhance the predictive rendering model for handling cursor movement and navigation, and perhaps extend the presentation language to allow predictive rendering for specialized cases, such as highlighting drop targets during dragging.
-- Main.MartijnSchrage - 25 Feb 2010