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