Creating custom widgets
If you're using EWS, you probably need to have some screen areas where interaction is special and not covered by standard widgets. In this chapter we will see how to build new widget classes that you will be able to customize in behavior and appearance
All EWS widgets should be instances of classes inheriting
WINDOW
(directly or indirectly). A simple case of new widget
is when you have to compose existing widgets (for example, a label, an
entry, and a button). In that case you just have to inherit
WINDOW
define a new constructor (or redefine make
),
making sure to call the original constructor from your initialization.
You can from there create subwidgets normally.
However, some times just composing existing widgets will not be enough. We will show an example of a widget that uses a custom display. This widget will be something similar to a progress bar, divided in two by a vertical line that can be moved with a widget routine.
To customize the window painting, you need to redefine the method
redraw
of the window. This routine takes a rectangle
indicating which part of the window needs to be updated (in window
local coordinates). So, if your window receives a redraw
call with a 10x10 rectangle at coordinates 0,0 it means that it should
repaint its top left 10x10 pixels. The basic implementation just calls
redraw on the window children; you should call it from your redefinition
if you want your childs to be visible.
The display can be updated using the drawing primitive
show_image
present in WINDOW
(At this time,
this is the only drawing primitive, more will be added later). This
routine takes a point, an image, and a clipping rectangle. This last parameter
should always be the rectangle you got passed in redraw
, or a
smaller one. Checking the source code of LABEL
at this moment
can be instructive. Actually, this drawing primitives should only be
called while doing a redraw.
So, let's now write our simple progress bar widget.
Source: progress_bar.e
class PROGRESS_BAR inherit WINDOW rename make as window_make redefine redraw end create make feature {NONE} -- Creation make (p: WINDOW; pos: RECTANGLE; new_progress: INTEGER) is do window_make (p, pos) set_progress (new_progress) create left.make (width, height, 0, 0, 255) -- left is blue create right.make (width, height, 0, 0, 55) -- right is dark blue end left, right: SDL_SOLID_IMAGE feature -- Access progress: INTEGER feature -- Operations set_progress (new_progress: INTEGER) is do progress := new_progress request_redraw_all end redraw (area: RECTANGLE) is local r: RECTANGLE do -- Draw left part r.set_with_size (0, 0, progress, height) -- left area show_image (left, 0, 0, r*area) -- Draw right part r.set_with_size (progress, 0, width-progress, height) -- right area show_image (right, progress, 0, r*area) end end
We can see that we are inheriting WINDOW
, redefining
handle_event
. Note also that make
is renamed
to make it possible to have a new make
with different
arguments.
make (p: WINDOW; pos: RECTANGLE; new_progress: INTEGER) is do window_make (p, pos) set_progress (new_progress) create left.make (width, height, 0, 0, 255) -- left is blue create right.make (width, height, 0, 0, 55) -- right is dark blue end
The creation routine make
calls the old creation
routine, and initializes the progress value. This is a simple progress
bar so the value will be in pixels, to avoid the geometry computations
required for a more realist progress bar. The progress will be stored
in the progress
attribute, and updated with
set_progress
.
To do the update, we will have two images, with a solid color, of the same size as the window. One will have the color of the left size of the bar, and the other the color of the right side. We will use these images as templates when painting the bar.
set_progress (new_progress: INTEGER) is do progress := new_progress request_redraw_all end
You might have noted that the set_progress
routine calls to
a request_redraw_all
; this routine is defined in
WINDOW
. When you make updates on the widget data that will
require updating the display, you should ask the display to update the area
where your widget is. request_redraw_all
takes care of that,
if you forget to call it, the display may not be updated (or updated later,
partially when some other widget requests for an update in an overlapping
area). Note that thew redraw will not be done inmediatly; as said in the
previous chapter, all the redraw is done after event processing.
redraw (area: RECTANGLE) is local r: RECTANGLE do -- Draw left part r.set_with_size (0, 0, progress, height) -- left area show_image (left, 0, 0, r*area) -- Draw right part r.set_with_size (progress, 0, width-progress, height) -- right area show_image (right, progress, 0, r*area) end
The redraw
method does some geometry and painting. The
rectangle r
is loaded with the widget area that should be
covered by the left side of the bar. After that, the image is shown clipped
at the intersection of r
(the extents of the left bar) and
area
(the area where the redraw was requested). Something
similar is done for the right side.
Write a small application to test this widget and play around with it. You might find useful to add a couple of buttons to change the progress value.
Some optimizations
As you may have noted, indicating explicitly which areas need to be
redrawn (with request_redraw_all
) allow to optimize the
display implementation, since not all the display needs to be updated,
but only the areas that are changing. EWS does that, which results in
important performance improvements.
As a widget programmer, it is a good idea to help EWS with that task,
indicating with more precision which areas need to be updated, and trying
to make that areas as small as possible. The routine request_redraw
is similar to request_redraw_all
but it allows you to be
more selective about the area to update. With that in mind, we can modify
the previous widget to update only the area between the old and new progress
positions. The changed routine looks like this
Source: progress_bar.e
set_progress (new_progress: INTEGER) is local r: RECTANGLE do r.set_with_coords ( progress.min (new_progress), 0, progress.max (new_progress), height) request_redraw (r) progress := new_progress end
This change does not make a correct request for redraw at widget creation;
however the default WINDOW
creation routine takes care of
requesting a full redraw the first time.
With this change, the progress bar will still look the same, but it will redraw much faster.
Adding user input
Now let's add some user interaction. Suppose we want to be able to update the bar by just moving the mouse on it; we will also make the value update smoothly, at a fixed real-time rate.
We need to get mouse events, to know where the mouse is moving, and timer events to the update. We redefine handle_event like this:
Source: progress_bar.e
mouse_position: INTEGER handle_event (e: EVENT) is local m: EVENT_MOUSE_MOVE t: EVENT_TIMER do m ?= e t ?= e if m /= Void then mouse_position := m.x elseif t /= Void then if mouse_position > progress then set_progress (progress + 1) elseif mouse_position < progress then set_progress (progress - 1) end end Precursor (e) end
This is a quite typical event handler. Assignment attempt is the usual way to recognize different types of events. Note that it helps you to check for event types and access specific data of the event (like the coordinates of the mouse movement). Besides, doing event recognition in that way ensure that new event types can be added without interference to existing handlers.
When testing this widget, remember to set the timer interval of the display to start getting timer events.
This finishes the tutorial. I suggest you to play with EWS to know it better. The included examples include one example of custom widgets (handling keyboard input), and reading the source code of the included widgets should be educative, most of them are quite short and simple.
Note that you can classify similar widgets using inheritance. You will see that done in the predefined widgets. In that way you can reuse behavior and operations and make widget coding even simpler.
If you found this tutorial useful, annoying, incomplete or anything, please send any suggestions or comments to dmoisset at grulic dot org dot arg. Thanks for using EWS!
Previous | Table of contents |