The idea I had for the patterned dirs was for it to look like stacked rolls of fabric, having some picture of yukata samples stacked on a rack floating in the back of my mind. So Google Image Search I went and gathered these:
Wallpaper rolls
Wallpaper samples on vertical sticks
Wallpaper samples
Fabric samples
More fabric samples
A whole wall of fabric samples
Yukata Patterns
Hexagons
Cool black-white-red tomesode half-way down the page
Lots of bright colors and textures
art with code
2008-12-07
How hex.hs works
I posted this small Haskell + Gtk2Hs graphics demo yesterday. Let me explain how it works.
Hex.hs is written in a pretty imperative fashion, it mostly happens inside Gtk2Hs's Cairo Render monad. The core of the top-level drawing loop works like this:
It draws a row of hexagons around a cylinder at y-offset
The
drawHexagons calls drawHexagon twice, drawing a \-segment of a hexagon row. The drawHexagon call does all the actual drawing and goes as follows:
Then we need the definition of a hexagon:
The function to project cylinder coordinates to display space:
And the 2D point transformations:
And there we have it. Create coordinates for the objects you want to draw, project them to the screen space, and draw them. Simple as pie.
Hex.hs is written in a pretty imperative fashion, it mostly happens inside Gtk2Hs's Cairo Render monad. The core of the top-level drawing loop works like this:
mapM_ (\y -> do
mapM_ (drawHexagons rotation cylinderRadius rowCount col) [col*2 .. col*2+rowCount/6-4])
[0..columns-1]
It draws a row of hexagons around a cylinder at y-offset
col, for x-offsets from col*2 to col*2 + rowCount/6 - 4. As the x-offset grows with the y-offset, the rows are offset from each other, forming a diagonal stripe moving down and to the right. But because we are drawing the hexagons on a cylinder, the stripe moves down and around the cylinder.The
rotation parameter gives the initial rotation of the cylinder coordinate system, and is based on the current time. As time changes, the rotation does too. And as we draw a new frame with a new time after having shown the previous one, we get an animation.drawHexagons calls drawHexagon twice, drawing a \-segment of a hexagon row. The drawHexagon call does all the actual drawing and goes as follows:
drawHexagon rotation cylinderRadius rowCount col row = do
-- offset odd rows down (remember that we draw like \\\\)
let y = if (floor row) `mod` 2 == 0 then 0 else 1.732
-- transform the hexagon from [-1..1] coordinates to the cylinder coordinate system
-- read from bottom up
let rhex = map (
scaleP (2*pi*r/rowCount) . -- scale up so that rowCount hexagons go around the cylinder
translateP (rowCount*rot/(2*pi) + row) (y+col*1.732*2) . -- move it to the wanted position
rotateP (pi/2) -- rotate the hexagon 90 degrees
) hexagon
-- project the hexagon from cylinder coordinates over to screen coordinates
let hex = map (cylinderProjection r) rhex
-- and draw the hexagon
save
newPath
uncurry moveTo $ head hex -- move to the first point of the hexagon
mapM_ (uncurry lineTo) $ tail hex -- apply lineTo to the rest of the points
closePath -- and close the path
setLineWidth 1
-- fill some of the hexagons and stroke the rest
if (floor (row+col)) `mod` 4 == 0
then fill
else stroke
restore
Then we need the definition of a hexagon:
-- ngon creates a regular polygon as a list of (x,y)-tuples in [-1..1] coordinate space.
ngon n =
map nrot [0..n-1]
where nrot i = let a = 2*pi*i/n in
(cos a, sin a)
hexagon = ngon 6
The function to project cylinder coordinates to display space:
-- maps the x-coordinate around a cylinder, growing right so that
-- 0 => 0, 0.5*pi*r => r, pi*r => 0, 1.5*pi*r => -r and 2*pi*r => 0
cylinderProjection r (x, y) = (r * sin (x/r), y)
And the 2D point transformations:
scaleP f (x,y) = (x*f, y*f)
translateP u v (x,y) = (x+u, y+v)
rotateP a (x,y) = (cos a * x - sin a * y, sin a * x + cos a * y)
And there we have it. Create coordinates for the objects you want to draw, project them to the screen space, and draw them. Simple as pie.
2008-12-06
A rotating cylinder of wireframe hexagons?
2008-12-05
Day 23: du traversal, semi-functional decoration and a progress animation

Du traversal is fast. But I'm not doing it right. So there's a gap between the FS model and the traversal model and it's going to cause me problems unless I go in and simplify everything.
In the picture on the left, you can see the traversal progress indicator rings. The other ring rotates clockwise and the other counter-clockwise. The effect is pretty nice and low-key. The red /-lines indicate that the directory is the current dir (thick line) or a child of the current dir, and make it easier to separate between deeper subdirs drawn large and top-level dirs.
The dots on the top of the directories.. I guess they make it easier to tell directories apart? And look cool? In a skyscraper warning lights / car backlights / dreadful gray boxscape contrastbringer kind of way.
(Pshh, Contrastbringer, the mighty demonic blade that feeds on the imagination of its victims.)
Parsing du output with C#
Tossing this out for you to shake your head at. And du doesn't give file count info easily, so I'm not parsing that yet. It's frickin fast though. And integrates kinda badly with my current way of doing traversal updates. How about I just rewrite everything in OCaml and cry?
For the heck of it, here's an OCaml version of the input parsing part:
And a Ruby version:
static void TraverseDir (string dirname)
{
if (TraversalCancelled) return;
FSEntry d = Get (dirname);
if (!StartTraversal (d)) return;
ProcessStartInfo psi = new ProcessStartInfo ();
psi.FileName = "du";
psi.Arguments = "-0 -P -b --apparent-size "+Helpers.EscapePath(dirname);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
Process p = Process.Start (psi);
using (BinaryReader b = new BinaryReader(p.StandardOutput.BaseStream)) {
while (true) {
string l = ReadNullTerminatedLine(b);
if (l.Length == 0) break;
ApplyDuString (l);
if (TraversalCancelled) {
p.Kill ();
return;
}
}
}
p.WaitForExit ();
}
static string ReadNullTerminatedLine(BinaryReader s)
{
byte[] buf = new byte[4096];
int i=0;
byte j;
try {
while ((j=s.ReadByte()) > 0) {
if (i > buf.Length) Array.Resize(ref buf, buf.Length*2);
buf[i] = j;
++i;
}
} catch (Exception) {}
Array.Resize(ref buf, i);
return new String(Array.ConvertAll(buf, Convert.ToChar));
}
static void ApplyDuString (string l)
{
char[] tab = {'\t'};
string[] size_date_path = l.Split(tab, 2);
Int64 size = Int64.Parse(size_date_path[0]);
string path = size_date_path[1];
lock (TraversalCache) {
TraversalCache[path] = new TraversalInfo(size, 0, DateTime.Now);
}
lock (Cache) {
if (Cache.ContainsKey(path))
SetCountAndSize(path, 0, size);
}
}
For the heck of it, here's an OCaml version of the input parsing part:
open Prelude
(* Hrm, I guess I do need something like gets *)
let input_nt_line ic =
let rec aux ic buf =
match optEOF input_char ic with
| Some '\000' -> buf
| Some b -> Buffer.add_char buf b; aux ic buf
| None -> buf in
let buf = aux ic (Buffer.create 256) in
if Buffer.length buf > 0
then Buffer.contents buf
else raise End_of_file
let traversal_cache = HHash.create 1000
let traverse_dir path =
let apply_du_line line =
let [size; path] = nsplit "\t" 2 line in
HHash.add traversal_cache path (parseInt size, 0, timeNow ()) in
withCmdStdout ["du"; "-0Pb"; "--apparent-size"; path]
(tokenizeIter input_nt_line apply_du_line)
let () =
traverse_dir ".";
puts (showInt @@ HHash.length traversal_cache)
And a Ruby version:
my_path = "."
traversal_cache = {}
IO.popen("du -0Pb --apparent-size '#{my_path.gsub("'", "\\\\'")}'", "r"){|f|
until f.eof?
sz, path = f.gets("\0").chomp("\0").split("\t",2)
traversal_cache[path] = [sz.to_i, 0, Time.now]
end
}
puts traversal_cache.size
Subscribe to:
Posts (Atom)



