Aug. 30, 2023, 11:40 a.m.

Managing Flutter versions and drawing circles the old way

Curiosities by Tensegritics

Welcome to the first installment of Tensegritics Curiosities our newsletter on ClojureDart, Clojure, algorithms and cool stuff!

Two topics today:

  • how to manage flutter versions between Clojuredart projects
  • Bresenham's line algorithm

How to manage flutter versions between Clojuredart projects (by Baptiste)

By default flutter is globally installed, this causes issues as soon as you have two projects and you upgrade Flutter for one project, only to discover later your other project is now broken.

The standard answer is to use fvm -- the Flutter version manager -- but then you have to prefix all flutter invocations by fvm, including invocations you have no control over like the ones by cljd.

Luckily direnv comes to the rescue! direnv is an utility which hooks into the shell to set environment variables depending on the current directory.

Once you have installed fvm and direnv, you can define flutter version per project:

cd path/to/my/new/or/existing/project
fvm use 3.13.1 #or whatever version you choose
echo "PATH_add .fvm/flutter_sdk/bin/" >> .envrc
direnv allow

You can check that flutter --version returns now the expected version.

To use a different version for this project, you now only need to do fvm use 3.10.0 for example -- no need to touch direnv anew.

Oh! I was going to forget: after changing Flutter version, always always always clean your project:

clj -M:cljd clean
flutter clean
flutter pub cache repair

Now you are good to go!

Bresenham's line algorithm (by cgrand)

Many years ago I revisited in Clojure some classic algorithms (like Tarjan's SCC) and Michael Fogus suggested me to cover Bresenham's line algorithm -- which I never did until today!

Early 90s, as a teenager I wondered how lines were efficiently drawn on computers. Pre-internet and in a rural town, I couldn't get an answer. Actually more than lines, circles puzzled me because the only way I knew to draw circles involved a for loop, cosine and sine to create a round-enough polygon or dense-enough sequence of pixels.

One day, bored away in front of a tiled floor, I realized that I could jump from one tile to the other following the simple rule that if I'm too close to the center I go to the tile right in front of me, else I go front and left (assuming the center was on my left). This could allow to draw one eighth of a circle, hence a full circle through symmetry!

This solution looked neat because it wouldn't produce polygons too coarse or unconnected pixels. However it used a square root for the distance: sqrt(x*x+y*y)<r. Luckily we could just compare the squares instead: x*x+y*y<r2 where r2 is the squared radius.

It was good but it still had two expensive multiplications (the Z80 I used didn't even have a MUL instruction).

Let's say we jumped on the front tile (y+1) then the new squared distance is x*x+(y+1)*(y+1) which expands into x*x+y*y+2*y+1, which is the previous distance plus 2*y+1! one increment, one bit shift, one addition instead of two multiplications! And the other case (going to x-1 y+1) can be simplified too: in this case the square distance change by 2*(y-x+1).

Here is the Clojure code:

(ns t10s.curiosities.issue1
  (:require ["package:flutter/material.dart" :as m]
            ["dart:ui" :as ui]
            [cljd.flutter :as f]))

(defn main []
  (f/run
    (m/MaterialApp
      .title "Welcome to Flutter"
      .theme (m/ThemeData .primarySwatch m.Colors/pink))
    .home
    (m/Scaffold
      .appBar (m/AppBar
                .title (m/Text "Drawing circles with only + and <<")))
    .body
    (m/CustomPaint .painter
      (reify :extends m/CustomPainter
        (paint [_ canvas size]
          (.translate canvas (/ (.-width size) 2) (/ (.-height size) 2))
          ; Obviously it's for educational purpose, on modern hardware it's counter-productive to do
          ; it like that! .drawPoints alone must be more complex than our circle computation
          (loop [x 110 y 0 e2 0]
            (when (<= y x)
              (.drawPoints canvas ui/PointMode.points
                [(m/Offset x y) (m/Offset (- x) y) (m/Offset (- x) (- y)) (m/Offset x (- y))
                 (m/Offset y x) (m/Offset y (- x)) (m/Offset (- y) (- x)) (m/Offset (- y) x)]
                (doto (m/Paint) (.-strokeWidth! 1)))
              (if (pos? e2)
                (recur (dec x) (inc y) (+ e2 (* 2 (- y x -1))))
                (recur x (inc y) (+ e2 (* 2 y) 1))))))
        (shouldRepaint [_ _] false)))
    (m/Container)))

Which displays as image.png

Now that I knew how to draw a circle efficiently, I turned my attention to lines, but this will be for the next Curiosities issue!

You just read issue #1 of Curiosities by Tensegritics. You can also browse the full archives of this newsletter.

Share on Twitter Share on LinkedIn Share on Hacker News Share on Reddit Share on Mastodon
This email brought to you by Buttondown, the easiest way to start and grow your newsletter.