The main goal of Crystal Space Windowing System (CSWS) is to provide a clean, effective and cross-platform base for creating CrystalSpace-related utilites. Currently there is only one such utility under development: CrystalSpace maze editor.
There is nothing new in CSWS regarding the window system. All base conceptions for building such an system were invented far ago, at least when first X-windows system was built (at MIT(?)) or even earlier.
The system is event-based, events are generated by hardware (mouse, keyboard) or software (components can send messages to each other). Event-system is system-dependent as well as graphics subsystem. They were developed first as part of CrystalSpace itself, then some modifications were made to better fit CSWS requirements. CSWS itself is completely system-independent, i.e. you can easily develop programs that will compile and work with no problems under any system supported by CrystalSpace.
Since Crystal Space always uses buffered visuals, the screen repainting is always done on the invisible page. This means flickering on redraw is almost impossible in CSWS, while it is common on usual GUIs with bad-written programs. If visual has more than one backbuffer (like OS/2 port), the image is automatically re-syncronized on all back-buffers.
Other CrystalSpace-specific feature is the division of event processing into "frames". Since CrystalSpace is a animation engine it operates in terms of frames, i.e. when the frame begins, all pending events in queue are processed, then all invalid components are redrawn, and at last the image is blitted onto the screen.
Coordinate system is based on most-used (wrong :-) system when top-left corner is (0,0) and bottom-right corner is (width, height). It would be better to use the 'right' coordinate system used, for examplem, in OS/2 and OpenGL but most programmers are already common with the 'wrong' system.
Most CSWS functions works with rectangles (csRect class). The csRect class has four data fields: xmin, xmax, ymin, ymax which are self-explanatory. Here is a example of rectangle (xmin = 0, ymin = 0, xmax = 3, ymax = 2):
0 1 2 3 4 ... | | | | | | 0 --@@@@@@@@@@--+--+-- @//|//|//@ | | 1 --@--+--+--@--+--+-- @//|//|//@ | | 2 --@@@@@@@@@@--+--+-- | | | | | | 3 --+--+--+--+--+--+-- | | | | | | ...--+--+--+--+--+--+--Vertical line 'X=3' and horizontal line 'Y=2' does NOT belong to the rectangle. The hashed pixels belong to the rectangle. So, if you have two windows: (0, 0) - (3, 2) and (3, 0) - (6, 2) they do NOT overlap.
CSWS has been designed to operate transparently independent of screen geometry or number of colors. To allow normal operation in different pixel format conditions colors are kept in a dynamically-built table which is referenced through indexes in that table. To allow different palettes for windowing system each component does not use colors directly when drawing, instead each component has a pointer to a table which contains color values. So, for example, a component can have a palette which's first element is used as background color, second is used to display text in window and so on. By changing only the palette table we can change at once how all these components are looking.
There is a number of pre-defined colors called cs_Color_XXX, for example cs_Color_Black, cs_Color_White etc. Components does not draw with cs_Color_XXX constants, they just indicate the index into their palette. All standard component palettes are contained in cswspal.cpp and cswspal.h files.
The base class for windowing system is csComponent. csComponent is a very complex class with lots of built-in functionality. This allows for easier creation of new components for specific needs. Most csComponent methods are virtual, allowing to override them as needed. There are some basic methods, which are described below. Other methods are described as needed during this document.
Each component should know how to paint itself. When windowing system thinks a part of a component image or entire component image is invalid (for example, when a window is moved from the top of other components), it adds that rectangle to the 'dirty' rectangle of the component. The dirty rectangle is initially empty, then all invalid rectangles are added to it. At the end of each frame windowing system sends a broadcast event with cscmdRedraw command code. When component catches this event, it calls Redraw method which in turn calls csComponent::Draw. After csComponent::Draw returns control, the dirty rectangle is emptied again.
All draw operations are clipped first against dirty rectangle and then against all component childs and neightbours. So drawing inside a component does not change even a pixel in other components, even if component is partialy covered by other components.
There is a number of drawing operations that you can use. Other operations can be synthesized from existing. All coordinates used for drawing are component-origin related, so (0,0) corresponds to component top-left corner.
Draw a line from (x1, y1) to (x2, y2). Example: Line (0, 0, 3, 3) produces the following output:
0 1 2 3 4 ... | | | | | | 0 --@--+--+--+--+--+-- |//| | | | | 1 --+--@--+--+--+--+-- | |//| | | | 2 --+--+--@--+--+--+-- | | |//| | | 3 --+--+--+--@--+--+-- | | | |//| | ...--+--+--+--+--+--+--
Draw a filled box that is closed between lines (X >= xmin), (X < xmax), (Y >= ymin) and (Y < ymax). Example: Box (0, 0, 3, 2) will produce the following output:
0 1 2 3 4 ... | | | | | | 0 --@--@--@--+--+--+-- |//|//|//| | | 1 --@--@--@--+--+--+-- |//|//|//| | | 2 --+--+--+--+--+--+-- | | | | | | ...--+--+--+--+--+--+--
Display a text string starting from (x, y) with color "foregroundindex" and background "backgroundindex". If backgroundindex == -1, the text is drawn transparently. To determint text width and height you can use the TextWidth (textstring) and TextHeight () functions. There is a number of built-in CSWS fonts, they can be used by calling SetFont (cs_Font_XXXX). Note that both TextWidth() and TextHeight() depends on current font.
Draw a 2D sprite or bitmap. "sprite2d" is a object of csSprite2D class. Sprite can have transparent colors, this way you can define "holes" in textures.
Draw a 3D-looking rectangle. This method just draws four lines to form a non-filled rectangle. Example: Rect3D (0, 0, 4, 4);
0 1 2 3 4 ... | | | | | | 0 --@--@--@--@--+--+-- |LL|LL|LL|DD| | 1 --@--+--+--@--+--+-- |LL| | |DD| | 2 --@--+--+--@--+--+-- |LL| | |DD| | 3 --@--@--@--@--+--+-- |LL|DD|DD|DD| | 4 --+--+--+--+--+--+-- | | | | | |
Draw a rectangle with oblique corners of given size. Example: ObliqueRect3D (0, 0, 10, 7, 3).
0 1 2 3 4 5 6 7 8 9 ... | | | | | | | | | | | 0 --+--+--@--@--@--@--@--@--@--@--+-- | | |LL|LL|LL|LL|LL|LL|LL|DD| 1 --+--@--+--+--+--+--+--+--+--@--+-- | |LL| | | | | | | |DD| 2 --@--+--+--+--+--+--+--+--+--@--+-- |LL| | | | | | | | |DD| 3 --@--+--+--+--+--+--+--+--+--@--+-- |LL| | | | | | | | |DD| 4 --@--+--+--+--+--+--+--+--+--@--+-- |LL| | | | | | | | |DD| 5 --@--+--+--+--+--+--+--+--@--+--+-- |LL| | | | | | | |DD| | 6 --@--@--@--@--@--@--@--@--+--+--+-- |LL|DD|DD|DD|DD|DD|DD|DD| | | ...--+--+--+--+--+--+--+--+--+--+--+--
Define a local clipping rectangle for subsequent drawing operations. For example, when drawing a button you may want to set clipping rectangle after drawing button borders so that button text accidentally won't draw over its borders even if it does not fit. To disable clipping you can define a empty rectangle (SetClipRect (0, 0, 0, 0));
This is the main component's entry point for handling windowing system events. Each time user presses a key, moves mouse etc windowing system generates a event and sends it to appropiate component. The normal event flow is top-down, i.e. the root window receives events first, then passes them if appropiate to its childs which passes it to their respective childs and so on. However, there are some basical constraints on event flow that you should know about:
There is another method called csComponent::PreHandleEvent (csEvent &Event) which is much like HandleEvent except that it is called for each event pulled from event queue before HandleEvent, and without rules above, i.e. any component can preprocess any hardware-generated event before it is sent along normal event handling chain. This can be used for popup menus, hot keys and so on. If PreHandleEvent returns true, the event is considered "eaten" and is discarded. Be careful with this feature to avoid interfering with normal component functions.
And last method related to event processing is csComponent::PostHandleEvent (csEvent &Event). It is called when no component handled the event and it is called for all components in parent->children order. It can be used to process events that nobody wants, for example it is used to process hotkeys on buttons (if active component is for example, a input line, and user presses 'A' key it should be entered into input text, while if active component is a non text-hungry component (for example, a listbox) the 'A' key can be used by some button to activate itself).
csComponent class has a number of data fields. The most important is
csRect bound;which defines the physical bounds of the component. All drawing operations happens inside and are clipped to this bound. Component does not have control over any other pixel outside this bound. The bounds are defined relative to parent's origin. For example, if we have a component with bounds (xmin = 20, ymin = 10, xmax = 100, ymax = 50) and it has a child (10, 10, 30, 40) then its real (physical) pixel coordinates are (20+10, 10+10, 20+30, 10+40). A child is always clipped to the bounds of all its parents (however, there is a exception from this rule).
int state;
contains the state flags of the component. There are two types of state flags (although CSWS does not make a difference between them) - dynamic flags and option flags. Option flags usually define some static aspect of component - for example, whenever component is selectable (i.e. if it can become the focused element), or it is partially transparent (irregularily-shaped components) etc. Dynamic flags often changes during component's life, for example component visibility flag, "component focused" flag and so on. The component state flags starts with the CSS_ prefix.
You should never change manually state flags. Instead, there are two methods for this: SetState (mask, state) and GetState (mask). For example, if component is not selectable and you want to make it selectable, call component.SetState (CSS_SELECTABLE, true). The SetState method is virtual and is often overriden to execute some specific functions when component state changes. For example, default SetState handler shows or hides the component if CSS_VISIBLE flag state changes.
csRect dirty;contains the "dirty" rectangle. If it is non-empty, the dirty area of component is redrawn just before the end-of-frame.
int DragStyle;Contains a combination of bits (CS_DRAG_XXX) that defines which sides of component can be dragged with mouse when you call first the csComponent::Drag method. By default it is equal to CS_DRAG_ALL. Note that if component won't call csComponent::Drag you won't be able to resize the component even if DragStyle will be equal to CS_DRAG_ALL.
csApp *app;This is the "main" application object, the root of entire window tree. It is a descendant of csApp class which is a superclass of csComponent. csApp has some application-global methods such as CaptureMouse, CaptureKeyboard, it contains the global mouse pointer object and many other.
csComponent *parent;Contains the parent component. Components are chained into a tree, each component knows its neightbours (through "next" and "prev" fields), each parent knows its focused child, and can traverse its list of childs by starting from focused, then going to focused->next and so on.
csComponent *prev, *next;Contains the next and previous neightbours in parent's child list. They are never NULL, chains are always closed. If a component is the only child of another, its next and prev fields points to himself.
csComponent *focused;This is the focused child component. For example, keyboard events are sent only to components in "focused" chain, i.e. to app, app->focused, app->focused->focused and so on. If component does not have any child components, focused is NULL. This is the only possible case when "focused" can be NULL.
csComponent *top;This is the top component in Z-order. Mouse events are processed in Z-order unlike keyboard events. The focused event is not neccessarily the top component, although usually this is true. If component has "CSS_TOPSELECT" flag set, when user activates (focuses) a component, it is made the topmost, however if it has this flag reset, its Z-order does not change.
int id;This is component's identifier. This is a user-definable value, but it is expected to be unique within all childs of a parent component. You can search for child components with given ID by calling parent->GetChild(int find_id).
There are several event types (other event types can be defined as needed, but existing set covers all current CSWS needs). There is the base event class called csEvent. It has several public data fields, the mostly used is Type which contains the event type. There are following currently defined event types:
if (Event.Key.ShiftKeys == CSMASK_ALT) { ... ALT + Event.Key.Code pressed ... }or,
if (Event.Key.ShiftKeys & CSMASK_ALT) { ... (at least ALT) + Event.Key.Code pressed ... }Also there is a bit in ShiftKeys called CSMASK_FIRST that is set only when key is pressed for first time. With this you can separate actual key presses from autorepeated keyboard events.
To check if the event is generated by keyboard, you can use the predefined IS_KEYBOARD_EVENT(Event) macro. To check for mouse events, you can use the IS_MOUSE_EVENT(Event) macro.
Clipping is the process of removing parts of output covered by other components. CSWS has full clipping implemented for all existing graphics primitives. There is no drawing operation that can influence other component's image (except transparent windows). Clipping is performed in the following way: First primitive is clipped against "dirty" rectangle (to cut off excessive output), then it is clipped against all its neightbours, then against "clip parent" window and all its neightbours and so on.
All components have a "parent" component and a "clipparent" component. What they are used for? Usually "clipparent" is equal to "parent" so there is no difference between them. But in some rare cases there is a need to perform clipping in a different way. For example, popup menus that belong to a window usually should not be clipped against that window, but rather to the parent of parent window or even clipped only to desktop. In this case you should assign a different clip parent to that component. This can be performed by using InsertClipChild() method. For example, to make a menu clip to application's bounds, you should call
app->InsertClipChild (menu);You should not assign directly to "clipparent" variable since InsertClipChild does many extra work, for example it inserts the so-called "clip child" into component's clip child list, removes component from former clipparent's "clip child" list and so on.
You can set a state flag called CSS_TRANSPARENT to mark semi-transparent components. Transparent components works differently in the following ways: