x
or y
coordinate).
The terms "min" and "max" are used for consistency with
the definition of Rectangle
.
<tile elements>= (<-U) [D->] int min, max;
<tile elements>+= (<-U) [<-D->] int base;
<tile elements>+= (<-U) [<-D->] int step;
<tile elements>+= (<-U) [<-D] TileList* list;
Instead of putting a pointer to a tile in all the windows and columns,
we simply insist that the first element in a window or column is a tile
structure. This way, (struct tile *) windowptr
makes sense.
Tile Lists
Tiles are strewn out in lists. Instead of using linked lists, I have
chosen to use arrays, but that is just an implementation detail.
TileLists need a rectangle to determine where its tiles go.
<data structures>+= (U->) [<-D] struct TileList { <tile list elements> };
The tile list consists of
self
is not used in anywhere in the tile code,
as the the tile list may be a member of a super-tile-list running
the other way.
<tile list elements>= (<-U) [D->] Tile self;
<tile list elements>+= (<-U) [<-D->] Tile **tiles;
<tile list elements>+= (<-U) [<-D->] int min, max;
<tile list elements>+= (<-U) [<-D->] ushort count, maxtiles;
hidden
is used to determine how many tiles
are hidden "to the left of" the tiles actually displayed (as a result of
a B3, for instance).
<tile list elements>+= (<-U) [<-D] ushort hidden;
};
<typedefs>= (U->) typedef struct TileList TileList;
Since the tile growth is rather blind at times, tile_list_reshaped
is called without a specific "untouchable" tile, thereby fixing a prior bug.
<public tile functions>= (U->) [D->] void tile_grow(Tile *tile, Growth grow) { TileList *list = tile->list; <tile_grow
locals> <tile consistency checks> switch (grow) { case Gsome: <growtile
a little> break; case Gmost: <growtile
lots> break; case Gall: <growtile
way lots> break; default: assert(grow != grow); } tile_list_reshaped(list, 0); }
<public function declarations>= (U->) [D->] void tile_grow(Tile *, Growth);
This is probably too small
, especially when a column is being grown, but it will work for now. Also, it does not check boundary conditions.<grow tile
a little>= (<-U)
tile->min -= tile->step;
tile->max += tile->step;
The B2 implementation of this code relies slightly on the way
tile_list_reshaped
is currently implemented, as it relies
on it not to hide any windows until it has tried shrinking
them all. Since this is good user interface policy anyhow, this
assumption presents little problem.
<grow tile
lots>= (<-U)
space = list->min;
for (i = list->hidden; i < list->count; i++) {
if (list->tiles[i] == tile) {
tile->min = space;
space = 0;
continue;
}
space += list->tiles[i]->base;
}
tile->max = list->max - space;
<tile_grow
locals>= (<-U)
int i, space;
To grow a tile way lots, the remaining tiles are added to the "hidden" list. For the moment, the tiles are added in order by simply swapping the selected tile to the end of the array, but heuristics should be added so that the "least hidden" tile is the one the user is most likely to want to see next.
A little trickery here -- the code only counts up to count - 1
.
This is because the tile being expanded way lots gets swapped to the end
position in the array during the loop. If the tile being expanded is already
at the end, then there is no problem. Of course, if the caller
called with an array where tile
was not in list
, then there
is a problem.
<grow tile
way lots>= (<-U)
for (i = list->hidden; i < list->count - 1; i++) {
if (list->tiles[i] == tile) {
list->tiles[i] = list->tiles[list->count - 1];
list->tiles[list->count - 1] = tile;
continue;
}
list->tiles[i]->min = list->tiles[i]->max;
}
list->hidden = list->count - 1;
tile->min = list->min;
tile->max = list->max;
L
ocating a Tile
This funtion simply relies on index_for_place
to find the right
tile and then looks the tile up in the array.
<public tile functions>+= (U->) [<-D->] Tile * point2tile(TileList *list, int spot) { int i = index_for_place(list, spot); check_list_consistency(list); return (i >= 0) ? list->tiles[i] : 0; }
<public function declarations>+= (U->) [<-D->] Tile *point2tile(TileList *, int);
M
aking a Tile Visible
As a gross heuristic,
the tile size is set to base
+3step
. If there is not enough room for
that, make the tile simply take the whole list over.
Of course, if the tile is already showing, there is no reason to expand it.
<public tile functions>+= (U->) [<-D->]
void
tile_show(Tile *tile) {
TileList *list;
<tile consistency checks>
list = tile->list;
check_list_consistency(list);
if (TILESIZE(tile) == 0) {
<expose the hidden tile tile
>
}
if (TILESIZE(tile) >= tile->base + tile->step)
return;
tile->max = tile->min + tile->base + 3 * tile->step;
if (tile->max > list->max) {
tile->max = list->max;
tile->min = tile->max - (tile->base + 3 * tile->step);
if (tile->min < list->min)
tile->min = list->min;
}
tile_list_reshaped(list, tile);
}
<public function declarations>+= (U->) [<-D->] void tile_show(Tile *);
Hidden tiles present a small problem -- they first must be "unhidden" so they can be displayed. For lack of a better place to put it,, the tile is added at the end of the tile list. +Errors windows probably belong at the bottom anyhow.
<expose the hidden tile tile
>= (<-U)
int i;
for (i = 0; i < list->count && tile != list->tiles[i]; i++)
;
while (++i < list->count)
list->tiles[i-1] = list->tiles[i];
list->tiles[i] = tile;
list->hidden--;
M
oving a Tile Within a Tile List
Just add and delete it for now.
<public tile functions>+= (U->) [<-D->] void tile_move(Tile *tile) { tile_del(tile); tile_add(tile->list, tile); }
<public function declarations>+= (U->) [<-D->] void tile_move(Tile *);
A
dding a Tile to a Tile List
This function uses the minimum coordinate of the new
tile to determine where it should go.
What if the caller doesn't care?
If the caller does not know how large it wants the tile, it should
specify a tile with a size of 0.
<public tile functions>+= (U->) [<-D->] void tile_add(TileList *list, Tile *tile) { <local variables for adding a tile> <tile consistency checks> check_list_consistency(list); <make sure tile shape is reasonable> <determine where to place the new tile in the list> <add the tile to the list> tile->list = list; tile_list_reshaped(list, tile); }
<public function declarations>+= (U->) [<-D->] void tile_add(TileList *, Tile *);
The tile location must be constrained to its collection. If the tile is so deformed after this reshaping that its size exceeds the bounds of its container, it is simply shrunk to zero size.
<make sure tile shape is reasonable>= (<-U) assert(tile->min < tile->max); if (tile->min < list->min) { tile->max += list->min - tile->min; tile->min = list->min; } if (tile->max > list->max) { tile->max = list->max; } if (tile->max - tile->min < tile->base) tile->min = tile->max = 0;
To determine where the tile should go in the list, we perform a
binary search on the tiles in the tile list to find the tile
containing tile->min
. This will be useful enough to warrant
a separate function. The program must then determine whether the
new tile will go before or after the tile it is being placed on.
The tile is placed so that the tile it is supplanting is moved as little
as possible.
<determine where to place the new tile in the list>= (<-U) if (list->count == list->hidden) { i = list->hidden; tile->min = list->min; tile->max = list->max; } else { i = index_for_place(list, tile->min); if ((<distance to move current tile up>) < (<distance to move current tile down>)) { i++; } }
<local variables for adding a tile>= (<-U) [D->] ushort i;
Moving a tile up means just moving it by the distance from its bottom to the desired top of the new window. Figuring out how far a tile down would move down is more complicated, but essentially works out to the distance from the top of the displaced tile to the bottom of the new tile.
<distance to move current tile up>= (<-U) list->tiles[i]->max - tile->min
<distance to move current tile down>= (<-U) tile->max - list->tiles[i]->min
Once we know where in the array the tile goes, the other tiles need to be shuffled out of the way.
<add the tile to the list>= (<-U) <make sure tile list has room for one more> for (j = ++list->count; j > i; j--) list->tiles[j] = list->tiles[j-1]; list->tiles[i] = tile;
<local variables for adding a tile>+= (<-U) [<-D] ushort j;
R
emoving a Tile from a List
This is pretty dull, actually -- the remaining elements
of the tile list are shuffled down in the array, and then
the tiles are resized to fit the new tile list.
One bug found: I was passing tile
into tile_list_reshaped
,
of all the silly things.
<public tile functions>+= (U->) [<-D->] void tile_del(Tile *tile) { TileList *list = tile->list; int i; for (i = 0; i < list->count; i++) { if (list->tiles[i] == tile) { while (++i < list->count) { list->tiles[i-1] = list->tiles[i]; } list->count--; tile_list_reshaped(list, 0); return; } } assert(0); }
<public function declarations>+= (U->) [<-D->] void tile_del(Tile *);
tile
argument
is used to indicate the tile that should not be adjusted.
Specifying a tile
of zero prevents any tile from being treated
as special.
Adjusting the tiles, then, is split into two phases -- adjusting
the tiles above tile
, and adjusting those below it. tileidx
is
used in the next chunk of code. It is initially set to a location
not in the tile list.
<public tile functions>+= (U->) [<-D->]
void
tile_list_reshaped(TileList *list, Tile *tile) {
int i;
int tileidx = -1;
<tile_list_reshaped
locals>
if (!tile)
adjust_sizes_in_range(list, list->hidden, list->count,
list->max - list->min);
else {
<tile consistency checks>
for (i = 0; list->tiles[i] != tile; i++) {
assert(i < list->count);
}
tileidx = i;
adjust_sizes_in_range(list, list->hidden, i, tile->min - list->min);
adjust_sizes_in_range(list, i + 1, list->count, list->max - tile->max);
}
<repair tile locations>
check_list_consistency(list);
}
<public function declarations>+= (U->) [<-D->] void tile_list_reshaped(TileList *, Tile *);
Once the tiles all fit, their locations must be updated
(adjust_sizes_in_range
does not do this). This is merely
a matter of sticking the tiles end-to-end.
<repair tile locations>= (<-U) for (prevmax = list->min, i = list->hidden; i < list->count; i++) { if (i != tileidx) { list->tiles[i]->max -= list->tiles[i]->min - prevmax; list->tiles[i]->min = prevmax; } prevmax = list->tiles[i]->max; }
<tile_list_reshaped
locals>= (<-U)
int prevmax;
The tiles in a particular range are adjusted through a simple series of steps. Note that this only adjusts the sizes of the tiles -- it does not adjust the tiles' locations.
Bugs found:
<static tile functions>= (U->) [D->] static void adjust_sizes_in_range(TileList *list, int start, int max, int available) { <local variables for tile adjustment> if (start == max) return; <determine amount of space needed> if (0 && needed < 0) { <expand tiles to fill slack> return; } <shrink tiles until space available> }
Determining the space required is easy -- determine the difference between the heights of the tiles and the collection they appear in.
<determine amount of space needed>= (<-U) needed = -available; for (i = start; i < max; i++) needed += TILESIZE(list->tiles[i]);
<local variables for tile adjustment>= (<-U) int i; int needed;
At this point, tiles are shrunk in a fairly arbitrary manner. The tile at the top gets shrunk first, and on down until the necessary space has been acquired. Tiles should be removed from view completely if adequate space is not available.
A small ugly here -- there may be a gap right before the "fixed" tile.
<shrink tiles until space available>= (<-U) for (i = start; needed && i < max; i++) { int base = list->tiles[i]->base; int step = list->tiles[i]->step; int shrink = TILESIZE(list->tiles[i]) - base; shrink = ((shrink - base) / step) * step + base; if (shrink > needed) shrink = needed; needed -= shrink; list->tiles[i]->max -= shrink; }
For the moment, expanding to fill a gap is handled is a relatively arbitrary manner -- the last tile is grown to fill the gap. What about any fuzz?
<expand tiles to fill slack>= (<-U) list->tiles[max-1]->max -= needed;
<public function declarations>+= (U->) [<-D->] #define COLTILE(t) (&(t)->tiles.self) static Col *WINCOL(Win *w) {return (Col *) w->tile.list;}
I
teration
There is no reason that every file dealing with tile lists need care
about the internals of the list. This also substantially helps if
the algorithm needs to be rearranged.
<public function declarations>+= (U->) [<-D->] #define FOR_ALL_TILES(t, l)\ { int __fral;\ for (__fral = 0; __fral < (l)->count && (t = (l)->tiles[__fral]); __fral++) #define FOR_ALL_SHOWING_TILES(t, l)\ { int __fral;\ for (__fral = (l)->hidden; __fral < (l)->count && (t = (l)->tiles[__fral]); __fral++) #define END_ALL }
C
omputing a Tile's Height
This is a trivial computation, but occurs frequently enough to
warrant its own macro.
<public function declarations>+= (U->) [<-D->] #define TILESIZE(tile) ((tile)->max - (tile)->min)
I
nternal Functions
<make sure tile list has room for one more>= (<-U) if (list->count == list->maxtiles) { list->maxtiles *= 2; list->tiles = realloc(list->tiles, list->maxtiles * sizeof(list->tiles[0])); }
start
is increased by at least 1 or end
is decreased by at
least one (because of C's rounding, start + (end - start / 2
is always less than start + (end - start)
).
<static tile functions>+= (U->) [<-D] static int index_for_place(TileList *list, int spot) { int start = list->hidden; int end = list->count; int mid; Tile *tile; while (start < end) { mid = start + (end - start) / 2; tile = list->tiles[mid]; if (spot < tile->min) end = mid; else if (spot > tile->max) start = mid + 1; else return mid; } return -1; }
A
ssertions
There will be some useful assertions to make at various points in the code.
Some are catastrophic, others are merely aeshetic problems.
<public tile functions>+= (U->) [<-D] void check_tile_consistency(Tile *tile) { <tile consistency checks> } void check_list_consistency(TileList *list) { int i; Tile *t; <tile list consistency checks> FOR_ALL_TILES(t, list) { check_tile_consistency(t); } END_ALL; }
<public function declarations>+= (U->) [<-D] void check_list_consistency(TileList *);
C
<tile list consistency checks>= (<-U) [D->] assert(list);
<tile list consistency checks>+= (<-U) [<-D->] assert(list->count <= list->maxtiles);
<tile list consistency checks>+= (<-U) [<-D->] assert(list->count == 0 || list->count > list->hidden);
<tile list consistency checks>+= (<-U) [<-D->] assert(list->count == 0 || list->tiles[list->hidden]->min >= list->min); assert(list->count == 0 || list->tiles[list->count - 1]->max <= list->max);
max
edge of one tile is not equal to the min
edge of the following tile.
<tile list consistency checks>+= (<-U) [<-D] for (i = list->hidden + 1; i < list->count; i++) assert(list->tiles[i-1]->max == list->tiles[i]->min);
tile.r.min
.
struct tile
is not consistent with
the information in the enclosing structure.
<tile consistency checks>= (<-U <-U <-U <-U <-U) assert(tile->min <= tile->max);
screen.r.max.y
.
font->height
above screen.r.max.x
.
<tiletypedef.h>= <typedefs>
<tiletype.h>= <data structures>
<tileproto.h>= <public function declarations>
<tile.c>= #include "wily.h" <static tile functions> <public tile functions>
tile
>: U1, D2
tile
a little>: U1, D2
tile
lots>: U1, D2
tile
way lots>: U1, D2
tile_grow
locals>: U1, D2
tile_list_reshaped
locals>: U1, D2