James Williams

Much Ado About Virtual Conferences

Tags: conferences speaking

As in-person conferences make their way onto more schedules, though it hasn't totally eliminated virtual conferences, folks are looking deeper into what they like and dislike about the virtual format.

It's on that backdrop that speaker and conference organizer Austin Parker wrote a post, Virtual Events Are Dead, Long Live Virtual Events, where he discusses some of the ways the format doesn't work for speakers or the audience. I found his argument compelling because he's been a speaker and a conference organizer. On the heels of that post, Lian Li invited Austin to participate in a Twitter Spaces panel titled "Back to normal? Is this the end of virtual tech events".

The panel discussion is a good complement to the blog post but it clocks in at 90 minutes covering a number of viewpoints from almost a dozen participants. They talked about the virtual events they attended, what they like and dislike about the format, its future, and how they might be made better. I wanted to share the info with my team in a form that was a little more digestible so made a zine style sketchnote of it. I added a bit of content about the flipped classroom that one panelist mentioned.

Sketchnote cover image depicting woman on airplane and at home with a dog wanting her to play

My Experiences with Virtual Events and Outlook

I've only given one virtual talk during the pandemic and that was pre-recorded with me in chat responding to questions. It was fine. I've done a lot of recorded videos in my current and previous DevRel roles. One of the things I liked about that previous role were the opportunities we had to be creative. Our video team was open to putting narrative vignettes between instructional modules. Those were often harder to land than the regular content because of costume concerns, how it will read, specific cultural references, etc. But when it lands well, it's really fun.

I think sometimes there is a hesitation to go a little bit out there and instead play to the lowest common denominator.

The only actual "register and block off time" conferences I've gone to were Google I/O and Android Dev Summit. IO was the more immersive of the two with a online "experience" space that you could navigate a character around and interact with.

The first year we had it in 2021, I remember some folks lamenting when the adventure portion was shut down for the year. I had coworkers who spent after hours time securing all the collectibles fishing and bumping into the things. Android Dev Summit was a more generic playlist of talks experience. I watched a bit on the day but most I ended up catching a couple weeks later. I didn't feel the pull to "attend" that I did when it was in person.

I think the virtual genie is out of the bottle and we've realized that while in-person is more optimal, virtual remote events can produce a good experience with enough effort. I do think the number of virtual events will decrease somewhat but am hopeful that we, as a community, work to make them better and rewarding to attendees and speakers. I want to explore speaking at more remote local events and engage with developers that wouldn't usually be able to make it to the events I usually frequent.

Fun with Fractals and Flame

Tags: game programming flutter flame

Fractals are patterns that are self-similar across scale, meaning a part of the object is similar to the whole. Their earliest applications revolved around simple recursion but today there is a whole field of study for generating fractals and the search to find formulas that will replicate nature. Fractals exist in many parts of nature and biology including trees, DNA, blood vessels, lighting, and some foods.

Before the first computer generated fractals were created, man-made fractals have been noticed in ancient and modern architecture and textiles. In game development, procedural generation of fractals or cellular automata (like Conway's Game of Life) are simple ways to provide everything needed for a game loop without needing a more defined game concept.

Barnsley Fern with many many many points

Sierpinski Carpet

A Sierpinski carpet attains its self-similarity but subdividing itself into a number of copies of the whole, removes one, and recurses on the subdivided copies. One formula to create a Sierpinski carpet is to:

  1. Start with a square.
  2. Subdivide it into 9 squares (3x3)
  3. Delete the center square.
  4. Repeat.

Sierpinski carpet with 1 iteration Sierpinski carpet with 5 iterations

import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/components.dart';

class SierpinskiCarpet extends FlameGame {
  late Size dimens;
  final RECURSIONS = 5;
  var white = Paint()..color = Colors.white;
  var black = Paint()..color = Colors.black;
  var blue = Paint()..color = Colors.blue;

  double shortestSide() {
    return dimens.width < dimens.height ? dimens.width : dimens.height;
  }
  @override
  Future onLoad() async{
    dimens = canvasSize.toSize();
    double square = shortestSide();
    var bounds = RectangleComponent(position:Vector2(0,0), size:Vector2.all(square), paint:blue);

    // Draw a white square matching the bounds
    add(bounds);

    punchCantorGasket(bounds.position.x, bounds.position.y, bounds.size.x, RECURSIONS);
  }
  void punchCantorGasket( double x, double y, double size, int recursions) {
    // Base case, if recursions = 0, return
    if (recursions == 0) {
      return;
    }

    double newSize = size / 3.0;
    double newSize2 = newSize * 2;
    var newRect = RectangleComponent(position: Vector2(x+newSize, y + newSize), size:Vector2.all(newSize), paint: black);
    add(newRect);

    recursions--;

    // Call punchCantorGasket on all 8 other squares
    punchCantorGasket(x, y, newSize, recursions); // 0,0
    punchCantorGasket(x, y + newSize, newSize, recursions); // 0,1
    punchCantorGasket(x, y + newSize2, newSize, recursions); // 0,2

    punchCantorGasket(x + newSize, y, newSize, recursions); // 1,0
    punchCantorGasket(x + newSize, y + newSize2, newSize, recursions); // 1, 2

    punchCantorGasket(x + newSize2, y, newSize, recursions); // 2,0
    punchCantorGasket(x + newSize2, y + newSize, newSize, recursions); // 2,1
    punchCantorGasket(x + newSize2, y + newSize2, newSize, recursions); // 2,2
  }
}
 

Barnsley Fern

Iterated function systems (IFS) create fractals by taking copies of itself, mutating the copy in some way and then unioned with the rest of the system. First described in the book Fractals Everywhere by its namesake Michael Barnsley, the Barnsley Fern simulates black spleenwort (Asplenium adiantum-nigrum).

Barnsley Fern

Barnsley Fern Formula

The IFS uses 4 transformations each with different probability frequencies shown in the table below.

wabcdefpPortion Generated
ƒ10000.16000.01Stem
ƒ20.850.04-0.040.8501.60.85Smaller leaflets
ƒ30.20-0.260.230.2201.60.07Largest left-hand leaflet
ƒ4-0.150.280.260.2400.440.07Largest right-hand leaflet
The starting point of the system is set to (0,0). The next point is created by calculating a random double and using its value to determine which transformation to apply with the last x and y coordinates resulting in the new location to plot. As more points are plotted, the form of the fern takes shape.
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:async' as Async;

class BarsleyFern extends FlameGame {
  final RADIUS = 0.6;

  final interval = Duration(milliseconds: 200);

  Random rnd = Random();
  Paint leafPaint = Paint()..color = Colors.green;
  Vector2 currentPoint = Vector2.all(0.0);

  @override
  Future onLoad() async {
    // Flame provides its own timer, and we want to use the core Flutter one.
    Async.Timer.periodic(interval, drawLeaves);
  }

  void drawLeaves(Async.Timer t) {
    for (var i = 0; i < 500; i++) {
      currentPoint = calculateNextPoint(currentPoint);
      drawCircle(currentPoint);
    }
  }


  Vector2 calculateNextPoint(Vector2 lastPoint) {
    var r = rnd.nextDouble();
    Vector2 nextPoint = Vector2.all(0.0);
    if (r < 0.01) {
      nextPoint.x = 0;
      nextPoint.y = 0.16 * lastPoint.y;
    }
    else if (r < 0.86) {
      nextPoint.x = 0.85 * lastPoint.x + 0.04 * lastPoint.y;
      nextPoint.y = -0.04 * lastPoint.x + 0.85 * lastPoint.y + 1.6;
    }
    else if (r < 0.93) {
      nextPoint.x = 0.20 * lastPoint.x - 0.26 * lastPoint.y;
      nextPoint.y = 0.23 * lastPoint.x + 0.22 * lastPoint.y + 1.6;
    }
    else {
      nextPoint.x = -0.15 * lastPoint.x + 0.28 * lastPoint.y;
      nextPoint.y = 0.26 * lastPoint.x + 0.24 * lastPoint.y + 0.44;
    }

    return nextPoint;
  }

  void drawCircle(Vector2 point) {
    // Scale point to canvas
    Vector2 plot = Vector2.all(0.0);
    plot.x = canvasSize.x * (point.x + 3) / 6;
    plot.y = canvasSize.y - (canvasSize.y * (point.y + 2)/14);
    add(CircleComponent(radius:RADIUS, paint:leafPaint, position: plot));
  }
}
 
This animation draws 500 points every 200 milliseconds.

Dragon Curve

A Lindenmayer system (L-system) uses an alphabet with production rules to expand the resulting string. Encoded into the alphabet are in instructions like moving a distance, rotating by an angle, or a combination of these rules. Aristid Lindenmayer, the theoretical botanist and biologist for whom they are named, used them to describe the growth of simple organisms such as bacteria. L-systems can be seen in herbaceous plants and trees. His work survives in a posthumously published book, The Algorithmic Beauty of Plants. The Dragon Curve is a fractal made by a single line naively by repeatedly folding each edge towards 90 degree angles. Dragon Curve fractal
Given the grammar:
Variables: F G

Constants: + -

Start: F
Angle: 90

Rules:
F -> F+G
G -> F-G
 
F and G are instructions to draw forward by a scalar amount. + means turn left by angle and - means turn right by that same angle. In the case of dragon curves, you don't need to recursively expand the string using the production rules. An alternate way of producing a dragon curve is to reduce the grammar to draw forward and do a left or right turn at the same time (L and R). You can now make a dragon curve by taking the output of the previous curve, adding a left turn and appending the result of reversing and inverting the previous curve.
StepExpansion
0L
1L + L + R
2LLR + L + LRR
3 LLRLLRR + L + LLRRLR
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import '../new_shape_components/line.dart';

/*
The Dragon Curve is a fractal made by a single line. It is formed of a series of turns, which can be constructed in the following way:
0: L
1: L + L + R
2: LLR + L + LRR
3: LLRLLRR + L + LLRRLRR
The nth dragon curve is the n-1th dragon curve plus L, plus the n-1th dragon curve reversed and reflected.
In this project we have split up the tasks of generating and drawing the dragon curve into separate classes.
 */

class DragonCurve extends FlameGame{
  final RECURSIONS = 15;

  @override
  Future onLoad() async {
    List dragonCurve = DragonCurveGenerator().generateDragonCurve(canvasSize.x.toInt(), canvasSize.y.toInt(), RECURSIONS);
    add(Polyline(dragonCurve, Paint()..color = Colors.green));
  }
}
enum Direction { LEFT, RIGHT}

class DragonCurveGenerator {
  Vector2 turn(Vector2 heading, Direction turn) {
    var newHeading = Vector2.all(0.0);
    if (turn == Direction.LEFT) {
      newHeading.x = -heading.y;
      newHeading.y = heading.x;
    } else {
      newHeading.x = heading.y;
      newHeading.y = -heading.x;
    }
    return newHeading;
  }

  List dragonTurns(int recursions) {
    List turns = [];
    turns.add(Direction.LEFT);

    for(int i = 0; i < recursions; i++) {
      // Add a left turn to turns
      turns.add(Direction.LEFT);
      // Add reflected version of reversed to turns
      for(int j = turns.length-2; j >= 0; j--) {
        if (turns[j] == Direction.LEFT)
          turns.add(Direction.RIGHT);
        else turns.add(Direction.LEFT);
      }
    }
    return turns;
  }

  List generateDragonCurve(int width, int height, int recursions) {
    var turns = dragonTurns(recursions);

    var head = Vector2(width/2, height/2);
    var heading = Vector2(5, 0);

    var curve = [];

    curve.add(Vector2(head.x, head.y));
    turns.forEach((turnInstruction) {
      heading = turn(heading, turnInstruction);
      head.x += heading.x;
      head.y += heading.y;
      curve.add(Vector2(head.x, head.y));
    });
    return curve;
  }
}
 

A lot of fractals will have multiple means to produce them.

Creating New Components with Flame

Tags: flame game programming flutter

In the last post, we worked through drawing and styling a couple of basic components in Flame. In this post, we'll create some new ShapeComponents and composite components.

How ShapeComponents are derived

The components we've used so far, RectangleComponent, CircleComponent, TextComponent all descend from PositionComponent allowing them to be moved around the screen, scaled, or rotated. CircleComponent and RectangleComponent inherit the ability to represent a geometric shape with a size and rotation angle from ShapeComponent.

Diagram showing the relationship between various Flame components

Two of the shapes that were missing when I started working with the library are lines and arcs.

A ShapeComponent must implement two functions: render(Canvas canvas) that draws your shape and containsPoint(Vector2 p) that will determine if a given point is within the shape's hitbox.

The Canvas is from the dart:ui package in core Dart. There are a lot of methods that aren't exposed directly to Flame.

Creating an Arc

There's already a function for drawing an arc in Flutter Canvas `drawArc`` that we can use encapsulate into a new ShapeComponent.

import 'dart:math';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';

class Arc extends ShapeComponent {
  Vector2 pos;
  double radius = 5.0;
  double startAngle = 0.0;
  /*  Start angle begins at right center
      and moves counterclockwise
      +----270---+
      |          |
      180        0
      |          |
      +----90----+
   */


  double sweepAngle = 360.0;
  bool isClosed = true;
  Paint paint = Paint()..color = Colors.green;

  Arc({required this.pos, required this.radius, required this.startAngle, required this.sweepAngle, required this.paint}) : super(
    position: pos,
  ) {
    add(CircleHitbox(position:this.pos, radius:this.radius));
  }

  @override
  bool containsPoint(Vector2 p) {
    // TODO: implement containsPoint
    throw UnimplementedError();
  }

  @override
  void render(Canvas canvas) {
    Rect r = Rect.fromLTWH(pos.x, pos.y, radius, radius);
    canvas.drawArc(r, startAngle * pi/180, sweepAngle * pi/180, isClosed, paint);
  }
}
 

The Arc class acts mostly as a wrapper for the properties needed for canvas.drawArc. I'm not doing any collision detection so the implementation of containsPoint is empty.

After creation, we can use the Arc like any other ShapeComponent.

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'arc.dart';

class CirclesAndArcs extends FlameGame {
  final strokePaint = Paint()
    ..color = Colors.red
    ..style = PaintingStyle.stroke;


  @override
  Future onLoad() async {
    add(
        CircleComponent(radius: 8.0, position: Vector2.all(100),
          paint:Paint()..color = Colors.white));

      var arc = Arc(pos:Vector2(150,100), radius: 50, startAngle:270.0, sweepAngle:270.0, paint:Paint()..color = Colors.green);
   add(arc);

    var arc2 = Arc(pos:Vector2(200,100), radius: 50, startAngle:0.0, sweepAngle:90.0, paint:strokePaint);
    arc2.isClosed = false;
    add(arc2);
  }
}
 

Creating Lines

If I needed to draw a single line, RectangleComponent would work just fine. For a component that joins every new segment to the end of the last segment, you need a new ShapeComponent.

A spiral drawn using line segments

The implementation of a Line and Polyline are similar to that of an arc, just enough to wrap the Canvas methods. LineShape draws a single line and Polyline draws a continuous line using a list of Vector2 vertices using each successive point to draw a line from the previous point.

import 'package:flame/src/game/notifying_vector2.dart';
import 'package:flame/components.dart';
import 'package:flame/geometry.dart';

import 'package:flutter/material.dart';

class LineShape extends ShapeComponent {
  LineSegment segment;
  NotifyingVector2 _position;
  double angle = 0.0;

  final DEFAULT_LINE = LineSegment(Vector2(0.0, 0.0), Vector2(1.0, 0.0));

  LineShape(this.segment, this._position, this.angle)
      : super(angle: angle, position: _position, size: Vector2.all(1));

  @override
  bool containsPoint(Vector2 point) {
    return false;
  }

  @override
  void render(Canvas canvas) {
    canvas.drawLine(segment.from.toOffset(), segment.to.toOffset(), paint);
  }
}

class Polyline extends ShapeComponent {
  var points = [];
  Paint paint = Paint()..color = Colors.red;

  Polyline(this.points, this.paint);

  @override
  bool containsPoint(Vector2 p) {
    return false;
  }

  @override
  void render(Canvas canvas) {
    for (int i = 0; i < points.length - 2; i++) {
      canvas.drawLine(points[i].toOffset(), points[i + 1].toOffset(), paint);
    }
  }
}
 

import 'package:flame/game.dart';
import 'package:flame/palette.dart';
import 'package:ud405_flame/stories/new_shape_components/line.dart';

class DrawASpiral extends FlameGame {
  final COILS = 20;

  @override
  Future onLoad() async {

    final green = BasicPalette.green.paint();

    final screenWidth = canvasSize.x;
    final screenHeight = canvasSize.y;

    final xStep = screenWidth / 2 / COILS;
    final yStep = screenHeight / 2 / COILS;

    var points = [];

    for (int i = 0; i < COILS; i++) {
      final xOffset = xStep * i;
      final yOffset = yStep * i;

      var point1 = Vector2((xOffset - xStep), yOffset);
      var point2 = Vector2((screenWidth - xOffset), yOffset);
      var point3 = Vector2((screenWidth - xOffset), (screenHeight - yOffset));
      var point4 = Vector2(xOffset, (screenHeight - yOffset));
      var point5 = Vector2(xOffset, (yOffset + yStep));

      points.addAll([point1, point2, point3, point4, point5]);
    }
    add(Polyline(points, green)); //, paint:green));
  }
}


 

Creating a composite Component

Next, we will create a reusable composite component composed of several ShapeComponents, a flower.

Our flower consists of a number of rectangles: - a long green rectangle for the stem, - two rotated green rectangles for the leaves, and, - set of rotated rectangles acting as petals.

Rectangular flower

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

import 'dart:ui' as ui;

class RectangularFlower extends FlameGame {


   final gradientPaint = Paint()
      ..shader = ui.Gradient.sweep(
        Offset(0, 0),
        [
          Colors.yellow.withOpacity(0.8),
          Colors.purple.withOpacity(0.4),
          Colors.deepPurple.withOpacity(0.9),
        ],
        [
          0.0,
          0.3,
          1.0,
        ],
       );

  @override
  Future onLoad() async {
    add(RectFlower(Vector2(100,0.0), Paint()..color = Colors.yellow));

    add(RectFlower(Vector2(300, 0), gradientPaint));
  }
}

class RectFlower extends PositionComponent {
  var green = Paint()..color = Colors.green;
  var darkGreen = Paint()..color = const Color(0xDD00CC32);
  var white = Paint()..color = Colors.white;
  var yellow = Paint()..color = const Color(0xFFEBE834);

  RectFlower(Vector2 position, Paint petalPaint){
    // Draw stem
    var stem = RectangleComponent(position: Vector2(48 + position.x, 100 + position.y), size: Vector2(10, 200), paint: green);
    add(stem);
    // Draw leaves
    var leaf1 = RectangleComponent(position: Vector2(40 + position.x , 180 + position.y), size: Vector2(40, 40), angle: 45, paint:darkGreen);
    var leaf2 = RectangleComponent(position: Vector2(85 + position.x, 230 + position.y), size: Vector2(40, 40), angle: 45, paint: darkGreen);
    add(leaf1);
    add(leaf2);

    // Draw petals
    const PETALS_COUNT = 20;
    for (int i = 0; i < PETALS_COUNT; i++) {
      var petal = RectangleComponent(position: Vector2(50 + position.x, 100 + position.y),
        size: Vector2(40, 40), angle: 360 * i/PETALS_COUNT, paint:petalPaint);
      add(petal);
    }
  }
}
 

RectFlower bases the locations of its child components rather arbitrarily from the top left corner. In its current state, the component isn't interactive and won't be able to respond to any bounds checking because the size defaults to zero.

In the game class, we can instantiate a couple of flowers and give their petals each a different paint.

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

import 'dart:ui' as ui;

class RectangularFlower extends FlameGame {


   final gradientPaint = Paint()
      ..shader = ui.Gradient.sweep(
        Offset(0, 0),
        [
          Colors.yellow.withOpacity(0.8),
          Colors.purple.withOpacity(0.4),
          Colors.deepPurple.withOpacity(0.9),
        ],
        [
          0.0,
          0.3,
          1.0,
        ],
       );

  @override
  Future onLoad() async {
    add(RectFlower(Vector2(100,0.0), Paint()..color = Colors.yellow));

    add(RectFlower(Vector2(300, 0), gradientPaint));
  }
}

class RectFlower extends PositionComponent {
  var green = Paint()..color = Colors.green;
  var darkGreen = Paint()..color = const Color(0xDD00CC32);
  var white = Paint()..color = Colors.white;
  var yellow = Paint()..color = const Color(0xFFEBE834);

  RectFlower(Vector2 position, Paint petalPaint){
    // Draw stem
    var stem = RectangleComponent(position: Vector2(48 + position.x, 100 + position.y), size: Vector2(10, 200), paint: green);
    add(stem);
    // Draw leaves
    var leaf1 = RectangleComponent(position: Vector2(40 + position.x , 180 + position.y), size: Vector2(40, 40), angle: 45, paint:darkGreen);
    var leaf2 = RectangleComponent(position: Vector2(85 + position.x, 230 + position.y), size: Vector2(40, 40), angle: 45, paint: darkGreen);
    add(leaf1);
    add(leaf2);

    // Draw petals
    const PETALS_COUNT = 20;
    for (int i = 0; i < PETALS_COUNT; i++) {
      var petal = RectangleComponent(position: Vector2(50 + position.x, 100 + position.y),
        size: Vector2(40, 40), angle: 360 * i/PETALS_COUNT, paint:petalPaint);
      add(petal);
    }
  }
}
 

Next time, we'll create some fractals.

Making Games with Flutter and Flame

Tags: flame game programming flutter

Like many people who become computer science majors, I wanted to make games as a kid. I even took a couple courses and fell accidentally into writing a book on HTML5 Game Programming. Later at Udacity, I was able to architect a short lived game programming credential using libGDX. And then I promptly did nothing for like five years.

The pandemic sparked an opportunity to revisit that hobby. Taking stock of the content I have, much of the HTML5 stuff is broken or uses libraries that don't exist anymore. The libGDX stuff is likewise not functional. That one is not because of the library itself. libGDX is alive and well but there have been lots of changes in Gradle (build system). It would be quite a bit of work update all the project files. Learning new stuff is more fun than maintenance so I'm learning a new framework, Flame. With the announcement of the Flutter Casual Games Toolkit at I/O this year, the choice seems serendipitous.

What is Flame?

Flame is a 2D game engine built on top of Flutter. Flame has a number of extension packages for things like audio, physics, collision detection, and level editing. Mobile, web, are desktop platforms are supported. It uses a component model where each component manages its own state and respond to events and draw calls.

Flame Components similar to Flutter and Widgets. They are the building blocks of your game.

Hello World

The main entry point for our examples, FlameGame is also a Component.

One of the first things I tried to do was to draw text. I initially feared it would be difficult once you start using different fonts. Sometimes that's the case in game frameworks, the default font is easy but you have to do all this . Fortunately, TextComponents derive much of their format from core Flutter. In the pubspec.yaml of a typical Flutter project, you'd specify the locations and common names for the fonts you intent to use.

Here is a portion of the pubspec.yaml declaring the fonts we'll use in the project.

  fonts:
     - family: Cinzel
       fonts:
         - asset: fonts/Cinzel-Medium.ttf
     - family: Corinthia
       fonts:
         - asset: fonts/Corinthia-Regular.ttf
     - family: Redacted Script
       fonts:
         - asset: fonts/RedactedScript-Regular.ttf
 

The game draws random strings in random locations around the screen using the above fonts.

Drawing various random words

TextComponent takes a text string and a TextPaint textRenderer. WordFactory selects a random word from the list and selects a random color and randomizes which preset font to display.

import 'dart:math';

import 'package:flame/components.dart';
import 'package:flame/game.dart';

import 'package:flutter/material.dart';

class WordCloud extends FlameGame {
  @override
  Future onLoad() async {
    const WORD_COUNT = 20;
    var factory = WordFactory(this.canvasSize);
    for (var i = 0; i < WORD_COUNT; i++) {
      add(factory.generateWord());
    }
  }
}

class WordFactory {
  Vector2 canvasSize;

  WordFactory(this.canvasSize);

  var rnd = Random();
  static const WORDS = [ /* List of words here */  ];

  TextPaint? generateRandomTextPaint() {
    var color = Color((rnd.nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
    var fontNum = rnd.nextInt(4);
    var fontSize = rnd.nextInt(48).toDouble();
    if (fontSize < 24)
      fontSize = 24;
    switch (fontNum) {
      case 0:
        return TextPaint(style: TextStyle(fontFamily: 'Redacted Script',color: color, fontSize: fontSize));
      case 1:
        return TextPaint(style: TextStyle(fontFamily: 'Cinzel' ,color: color, fontSize: fontSize));
      case 2:
        return TextPaint(style: TextStyle(fontFamily: 'Corinthia', color: color, fontSize: fontSize));
      default:
        return TextPaint(style: TextStyle(color: color, fontSize: fontSize));
    }
  }

  TextComponent generateWord() {
    var word = WORDS[rnd.nextInt(WORDS.length)];
    var x = rnd.nextInt(canvasSize.x.toInt());
    var y = rnd.nextInt(canvasSize.y.toInt());
    return TextComponent(text: word, textRenderer: generateRandomTextPaint())
      ..x = x.toDouble()
      ..y = y.toDouble();
  }
}
 

You can expect to spend as much time in general Flutter documentation as you will searching Flame APIs.

Drawing A Starfield

For the next example, I wrote a small bit of code to draw a bunch of components on screen. There isn't a PointComponent in Flame but a point is really just a very small rectangle. Using the canvas size and a desired density of points, the code creates a list of randomized points and adds them to the component tree.

Starfield

import 'dart:math' as math;

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';


class DrawAStarfield extends FlameGame {
  final starPaint = Paint()..color = Colors.white;

  final density = 0.2;
  var stars = [];

  @override
  Future onLoad() async {
    var canvasSize = this.canvasSize.toSize();
    var width = canvasSize.width;
    var height = canvasSize.height;
    var size = 10000 * density;
    var r = math.Random();

    for (int i = 0; i < size; i++) {
      var x = r.nextDouble() * width;
      var y = r.nextDouble() * height;
      stars.add(Vector2(x, y));
    }

    for (int j = 0; j < size; j++) {
      add(RectangleComponent(position: stars[j], size: Vector2(1, 1),
          paint: starPaint));
    }
  }
}
 

Drawing Lines

DrawLines is pretty much the previous snippet but with more paints. First a couple different colors and then a LinearGradient with various colors as the stop points.

Lines using various Paints

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

import 'dart:ui' as ui;

class DrawLines extends FlameGame {
  final white = Paint()..color = Colors.white;
  final magenta = Paint()..color = Color(0xFFFF00FF);
  final red = Paint()..color = Colors.red;
  final blue = Paint()..color = Colors.blue;
  final green = Paint()..color = Colors.green;

  @override
  Future onLoad() async {
    add(
        RectangleComponent(position: Vector2(0, 100), size: Vector2(500, 2), angle: 45,
            paint:white));
    add(
        RectangleComponent(
            position: Vector2(150, 100), size: Vector2(500, 2), angle: 45,
            paint:magenta));

    add(
        RectangleComponent(
            position: Vector2(200, 100), size: Vector2(500, 2), angle: 45,
            paint:green));

    final gradientPaint = Paint()
      ..shader = ui.Gradient.linear(
        Offset(70, 100),
        Offset(500, 20),
        [
          Colors.green.withOpacity(1.0),
          Colors.blue.withOpacity(0.2),
          Colors.yellow.withOpacity(0.8),
          Colors.red.withOpacity(0.1),
          Colors.red.withOpacity(0.9),
        ],
        [
          0.0,
          0.5,
          0.7,
          0.9,
          1.0,
        ],
      );

    add(RectangleComponent(
        position: Vector2(70, 100), size: Vector2(500, 20), angle: 45,
        paint:gradientPaint));
  }
}
 

If you don't already have good Flutter fundamentals, working with Flame will definitely get you up to speed.

Emacs as an IDE: Android, Java, and Kotlin

Tags: Emacs Programming Java Android Kotlin

In the two previous posts, I covered basic Emacs setup and Flutter support. I'll now be tackling the other area that consumes most of my work development time, Java/Kotlin and Android.

Installing Java, Kotlin, and Gradle with SDKMAN

My preferred way to install Java and Kotlin is sdkman.io which allows you to install several Java and Kotlin versions along side each other and easily switch between them. To run Android projects, you only specifically need to install Java which all versions of Gradle requires to run. Many Android projects will include a gradlew script that will download Gradle at build time but I like to install a specific version of Gradle because the wrapper will redownload executables and dependencies.

sdk i java 11.0.13.fx
sdk i kotlin
sdk i gradle

Latest and greatest is generally fine for Kotlin and Gradle. For Java, I like to use JDK11.

Adding Java, Kotlin and Android support to Emacs

Again, we venture to Doom's init.el file (SPC f P), we need to add enable some options.

In the :lang section, we need to modify the java and kotlin lines as follows:

(java +lsp)
(kotlin +lsp)

Enabling the Java module enables Java, Groovy, and Android support. The Android features allow you to create, control, and debug Android projects from Emacs with some keybindings for common operations. You can even control emulators. The Kotlin module provides a Kotlin REPL and its own Gradle support.

Setting up Java and Kotlin LSPs

Java's LSP server is a part of the default doom-emacs build so all you need to do to install is executing M-x lsp-install-server and select jdtls.

If you change Java versions, you need to uninstall and reinstall the server.

M-x +lsp/uninstall-server
M-x lsp-install-server

Kotlin, on the other hand, requires a little bit more work. LSP support is provided via a community developed LSP server at https://github.com/fwcd/kotlin-language-server.

Clone the repo, run gradlew :server:distZip to get an archive of the language server and its dependencies. The compiled zip will be in the /server/build/distributions folder. Extract that somewhere and add the path to your config.el doom file. Add the Java path for good measure

(add-to-list 'execpath "<path to kotlin-language-server>")
(setenv "JAVA_HOME" "<java home path>")

Finally, reload(SPC h r r) your configuration and you are good to go. Here's a quick video showing how moving around a project would work.

Emacs as an IDE: Flutter

Tags: Emacs Programming Flutter

In the last post, we did the basic quality of life setup to make Emacs into a general purpose supped up text editor. This post will walk you through setting up Flutter IDE support rivaling that of VS Code or Android Studio.

Before starting you should install Flutter using the instructions on flutter.dev. Make a note of where Flutter is installed on your machine. Some things will work out of the box and others will require paths to be specified.

Adding Flutter and Dart support to Emacs

Again we need to go back to the Doom init.el file (SPC f P) and add enable some options.

In the :lang section, uncomment dart and add +lsp and +flutter. Dart and Flutter use yaml configuration files for dependencies so you can optionally uncomment yaml to enable some basic highlighting.

(dart +flutter +lsp)   ; paint ui and not much else
;; ...
yaml                   ; JSON, but readable

Even if you are able to run Flutter or Dart from a command-line prompt, Emacs doesn't always pick up your path so it's best to specify them. We'll need to declare the following variables:

lsp-dart-sdk-dir          ;; location of Dart SDK
lsp-dart-flutter-sdk-dir  ;; location of Flutter SDK
flutter-sdk-path          ;; location of Flutter SDK

The Flutter SDK needs to be defined for both twice because under the covers there are two different tools providing Flutter support. Given that FLUTTER_DIR is the base directory of your Flutter install, add the following to your config.el file.

(setq lsp-dart-sdk-dir "FLUTTER_DIR/bin/cache/dart-sdk")
(setq lsp-dart-flutter-sdk "FLUTTER_DIR")
(setq flutter-sdk-path "FLUTTER_DIR")

Finally, reload(SPC h r r) your configuration and you are good to go. You can read more about the features of lsp-dart.

At this point most commands you could do in say Android Studio are some variant of lsp-dart-* with mostly key bindings for testing. Running a Flutter app or doing a hot reload is bound to SPC m r. Some of the ones I use a lot that don't yet have bindings are:

lsp-dart-pub-get                ;; executes pub get or flutter pub get
lsp-dart-pub-upgrade            ;; executes pub upgrade or flutter pub upgrade
lsp-dart-show-outline           ;; displays the structure of the Dart file
lsp-dart-show-flutter-output    ;; displays Flutter widget structure

Here's how my flow looks when working with Flutter.

Emacs as an IDE: Setup

Tags: Emacs Programming

I found myself in emacsland through somewhat unconventional means. It wasn't a parent, mentor, or king of the geeks that turned me on to it, it was grad school. And it was one class in particular, Analysis of Algorithms. To complete the homework assignments for the class, I had to create lots and lots of mathematical equations, graphs, and diagrams. Drawing them by hand was acceptable but no one wants to draw a five or six level diagram by hand.

The one sensible way to do that was LaTeX, a typesetting language created in the 1980s but still in use for print technical books. Emacs org-mode, a likely subject for a subsequent post, has extensive LaTeX support and my time to complete homework was cut by a third.

Application of B-Tree Insert Algorithm

From there I branched out into the other capabilities of emacs, taking advantage of its high level of customization, rich set of plugins, and existence on almost every modern computing platform.

I'll answer the burning question in advance, "why not VS CODE?" I started using emacs before VS Code was a thing. I also like having everything under one roof. Org-mode is an emacs-exclusive feature that I use for document creation and personal productivity. So I'd be spending time here anyways.

Emacs users often share their configurations online in dotfiles repos and there's a culture of learning by example vs formal education. Packages for almost every purpose imaginable are available in various repositories so the joke about users endlessly tweaking their configuration can ring true. This series of posts will not go to far down that path. Mind you, I have been that user but have trimmed down the hand rolled stuff. There will be opportunities to tweak things but we'll be starting from an opinionated base "distro" (a collection of plugins, platforms and processes) called doom-emacs.

Why doom ?

First it's to combat bad ergonomics of a default Emacs config. Long story short, Emacs uses the CTRL and ALT keys a lot for UI navigation. These keys are often at the extreme left and right of the keyboard and a reduced size key as well. There are a lot of other examples where the application shows its age, pre-dating many of the conventions of the last 40 years.

For example, in normal Emacs, you save a file with CTRL+x CTRL+s. In doom-emacs, saving a file is completed with SPC f s. Space is the system wide leader key that brings up a popup window. f is the context for file operations and s saves the file. This organization system makes it easier to discover commands quickly.

Doom emacs menu subsystem

Doom provides a couple different ways to install packages: through normal repositories like MELPA or using its managed module system. Each module will specify packages to install if that module is enabled, associated functions and commands that should be available to the user and binds them to context menus. For example, when you enable the Java module, it knows that most Java builds use Gradle/Groovy so those are installed along side the Java packages.

Doom is also really fast. With a spartan build, it is ready to use in a couple seconds, my full build loads in just under 10s. Much much much faster than I could do with IntelliJ.

Install Emacs and doom-emacs

Doom has a pretty good guide to install Emacs and doom.

It covers the quirks of getting Emacs installed on different platforms and the two lines of code to get doom installed:

git clone https://github.com/hlissner/doom-emacs ~/.emacs.d
~/.emacs.d/bin/doom install

The doom command is your main way to update and sync your installed packages as well as troubleshoot problems with your install(doctor). After this first instance, you can update, sync, and reload your configuration from inside Emacs.

Understanding the module system

Your doom configuration lives in three files: init.el, config.el, and packages.el. Most of the time, you'll be editing init.el as it controls which modules are installed. Modules are grouped by general function areas. Here is a subset of the module groups, listing only the ones we care about.

  • input, alternate keyboard layouts and Japanese/Chinese input
  • completion, code completion and file/command search manipulation
  • ui, modules that modify the general app user interface
  • editor, modules that make Emacs more IDE like (snippets, text manipulation, cursor niceties, etc)
  • emacs, core emacs functionality
  • term, options to run a terminal in Emacs
  • checkers, syntax and grammar checkers
  • tools, general small to medium scale apps and integrations (git, docker, password managers, etc )
  • lang, programming language support
  • app, large scale apps that tailor emacs to that purpose like social media clients, RSS readers and media players

You can peruse the list by running SPC f P. The capital P is important.

Navigation and working with files

Here are a couple of basic doom emacs commands.

Doom emacs File submenu

  • Open file SPC f f
  • Save file SPC f s
  • Open configuration files SPC f P
  • Change to insert mode i
  • Exit insert mode ESC
  • Run a command M-x (read as Meta, it's usually the Alt key)

Emacs needs to differentiate between when you want to edit the contents of a file and when you want to enter commands. You can tell if you are in insert mode or not based on the cursor shape. | is insert mode, █ is command mode. Press i to start editing files.

Setting up basic IDE features

By default, Doom enables a number of defaults. To make it more like a traditional IDE, we can add a project drawer for easy navigation, tabs to switch between files in a project more easily, github style emoji and terminal support. We can also enable a side minimap to navigate inside a file quickly.

In the :ui section of init.el, uncomment/add [remove the ;;]:

minimap
tabs
treemacs
(emoji +github)

In the :term section, uncomment:

term

Exit insert mode (ESC), press SPC f s to save the file and then SPC h r r or alternatively run M-x doom/reload to reload the config.

An project will look like this:

Treemacs and Minimap in emacs

Pretty cool but we add some language support.

Tweaking fonts and themes

Emacs can use an installed system font as the editor font. The default is perfectly serviceable but I like to use Fira Code.

The font is set in the config.el file, usually commented out at first. The following sets 16pt Fira Code Retina as the default UI font.

(setq doom-font (font-spec :family "Fira Code Retina" :size 16))

Doom has over 70 included themes, you can apply a different one using M-x load-theme or SPC h t.

doom-one is the default, check this repo for descriptions of the included themes.

You may get the prompts Loading a theme can run Lisp code. Really load? and Treat this theme as safe in future sessions?. Answer yes to both.

This will activate the theme for only the current session, after which it will revert back to doom-one. To make it permanent, update the theme in the config.el file. The following assigns doom-dark+ permanently.

(setq doom-theme 'doom-dark+)

Language Server Protocol and Debugging

Some of the key features that separates the text based editors like emacs and vim from more heavy IDEs like Android Studio or XCode are things like refactoring, linting and code completion. In the past, you had to forgo all that if you wanted a lightweight portable solution. That's no longer the case.

The Language Server Protocol allows a server to be notified when changes in a document happen. These changes are analyzed and the client is sent a message noting any errors or warnings that have come with the change. The language server can also include possible linting optimizations the user can perform as well as navigating to signatures/defintions of classes and functions.

LSP started as a Microsoft Visual Studio Code feature but developed as its own specification and can be used in almost any text editor that's capable of sending and consuming JSON-RPC messages.

LangServer.org maintains a list of well supported LSPs. I'll be covering the specifics for setting up support for the languages individually but for now, we need to set up global support. In tools section of init.el, enable:

:tools
;; ...
lsp
(debugger +lsp)

Company-mode, short for complete anything is a text completion framework that you get for free with enabling doom's LSP support.

The Debug Adapter Protocol, modeled somewhat like LSP, is a means to provide language and tooling debugging to users. Tooling only needs the common interface of debug adapters. Each debug adapter then handles the specifics of interacting with the underlying debugging tool. Emacs dap-mode hasn't hit 1.0 so it doesn't have as much language coverage as LSP.

The following blog posts will cover the various programming languages and writing environments I use. First up is Flutter.

Understanding CustomPaint, CustomPainter, and the Canvas

Tags: Flutter Programming

Side by side image of DartPad and a recursive drawing

In my last post, we talked about a generalized strategy for approaching the Flutter Clock Challenge. In this post, we'll dig into drawing on the canvas using the CustomPaint widget.

CustomPaint and CustomPainter

For all the powerful things you can do with a CustomPaint, its API surface is relatively small. It allows you set a CustomPainter that serves as a background for the widget, an optional child that will draw itself above the painter, and an optional foreground painter to draw in front of the child widget.

CustomPaint( 
    painter: , 
    child: , /* optional */ 
    foregroundPainter: , /* optional */ 
) 

The real power comes from CustomPainter and its use of the canvas. CustomPainter has the following signatures.

class MyClass extends CustomPainter { 
   @override void paint(Canvas size, Size size) { /*...*/ }          

   @override bool shouldRepaint(CustomPainter oldDelegate) { } 
} 

Canvas

The Canvas class exposes drawing commands for a number of operations including:

  • points, lines, arcs/ellipses, and polygonal shapes,

  • paths,

  • text,

  • shadows, and,

  • clipping.

Most of the operations take the form of draw* and may take an Offset, individual double s, and a Paint to draw the object. One thing to keep an eye on if coming a graphics package in another language is that Offset changes what it means depending on where it is used. Sometimes, it's an delta from another coordinate, other times it represents a position. From the Flutter docs,

Generally speaking, Offsets can be interpreted in two ways:

  1. As representing a point in Cartesian space a specified distance from a separately-maintained origin. For example, the top-left position of children in the RenderBox protocol is typically represented as an Offset from the top left of the parent box.

  2. As a vector that can be applied to coordinates. For example, when painting a RenderObject, the parent is passed an Offset from the screen's origin which it can add to the offsets of its children to find the Offset from the screen's origin to each of the children.

Here's a painter that draws a target on a canvas using circles with alternating colors and decreasing radii. For the paints, I used the built-in Material Colors class. You can alternatively specify a color directly in hex using AARRGGBB format.

class TargetPainter extends CustomPainter {

@override
void paint(Canvas canvas, Size size) {
    // Offset sets each circle's center
    canvas.drawCircle(
        Offset(200,200), 200, Paint()..color = Colors.white);

    canvas.drawCircle(
        Offset(200,200), 150, Paint()..color = Colors.red);

    canvas.drawCircle(
        Offset(200,200), 100, Paint()..color = Colors.white);

    canvas.drawCircle(
        Offset(200,200), 50, Paint()..color = Color(0xFFFF0000);   
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => true;

} 

TargetPainter shown in DartPad

Here's another painter creating a Sierpinksi carpet (shown in article hero image), a plane fractal formed by subdividing the plane into 9 parts, removing the center and then recursively subdividing those parts.

class SierpinskiCarpet extends CustomPainter {
  Size dimens;

  int RECURSIONS = 5;

  SierpinskiCarpet(this.dimens);

  double shortestSide() {
    return dimens.width < dimens.height ? dimens.width : dimens.height;
  }

  @override 
  void paint(Canvas c, Size size) {
    double square = shortestSide();
    Rect bounds = Rect.fromLTWH(0, 0, square, square);

    // Draw a white square matching the bounds
    c.drawRect(bounds, Paint()..color=Colors.white);
    punchCantorGasket(c, bounds.left, bounds.top, bounds.width, RECURSIONS);
  }

  void punchCantorGasket(
      Canvas c, double x, double y, double size, int recursions) {
    // Base case, if recursions = 0, return
    if (recursions == 0) {
      return;
    }

    double newSize = size / 3.0;
    double newSize2 = newSize * 2;
    c.drawRect(Rect.fromLTWH(x + newSize, y + newSize, newSize, newSize),
        Paint()..color=Colors.black);

    recursions--;

    /* Call punchCantorGasket on all 8 other squares */
    punchCantorGasket(c, x, y, newSize, recursions);            /* 0,0 */
    punchCantorGasket(c, x, y + newSize, newSize, recursions);  /* 0,1 */
    punchCantorGasket(c, x, y + newSize2, newSize, recursions); /* 0,2 */

    punchCantorGasket(c, x + newSize, y, newSize, recursions);  /* 1,0 */
    punchCantorGasket(
        c, x + newSize, y + newSize2, newSize, recursions);     /* 1, 2 */

    punchCantorGasket(c, x + newSize2, y, newSize, recursions); /* 2,0 */
    punchCantorGasket(
        c, x + newSize2, y + newSize, newSize, recursions);     /* 2,1 */
    punchCantorGasket(
        c, x + newSize2, y + newSize2, newSize, recursions);    /* 2,2 */
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
} 

Transforming objects

Drawing a circle or rectangle where we can directly specify the constraints is fine but what happens when we can't directly set a position, or need to rotate or scale. The short answer is that canvas provides answers to translate, scale, and rotate. The long answer is that the order of your transformations matter. They get boiled down into a transformation matrix(an evil math construct) and individual collections of transforms can be made affect all or parts of a drawing using save and restore.

In this snippet of a painter, to draw the leaves of the flower, we save the state of the transform matrix with canvas.save(), do our transforms and drawing and then restore the previous state when done.

The transform matrix works like a Stack. Each new draw instruction looks at the accumulated matrix from the stack, applies it and draws the object. When canvas.restore is called, the previous saved state is popped from the stack. In the case of the flower, the (0,300) translation is done before the first save. So it will affect all the subsequent drawing calls but any transforms enclosed in save/restore will not leak to others.

@override
  void paint(Canvas canvas, Size size) {
    // Draw background
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), white_paint);

    canvas.translate(0,300);
    // Draw stem
    Rect stem = Rect.fromLTWH(100, 0, 10, 200);
    canvas.drawRect(stem, green_paint);

    // Draw rotated leaf
    canvas.save();
    canvas.translate(110, 100);
    canvas.rotate(135 * pi / 180);
    Rect leaf1 = Rect.fromLTWH(0, 0, 40, 40);
    canvas.drawRect(leaf1, green_paint);

    canvas.restore();

    // Draw rotated leaf
    canvas.save();
    canvas.translate(100, 150);
    canvas.rotate(315 * pi / 180);

    canvas.drawRect(leaf1, green_paint);
    canvas.restore();

    // Draw petals
    canvas.save();
    canvas.translate(100, 0);
    int petals_count = 20;
    Rect petal = Rect.fromLTWH(0, 0, 40, 40);
    for (int i = 0; i < petals_count; i++) {
      canvas.save();
      canvas.rotate((360.0 * i/petals_count) * pi / 180);
      canvas.drawRect(petal, yellow_paint);
      canvas.restore();
    }

    canvas.restore();
  } 

Dartpad running the flower painter code

Rendering Paths

The canvas can also render paths. The following graphic was derived from an Inkscape drawing. Flutter's Path class provides many functions mirroring SVG paths to create complex paths. Given the right calculations, you can simulate 3D like in the following painter.

class _3DBoxPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Rect.fromLTWH(0,0,200,200), Paint()..color=Colors.white);

    canvas.scale(5,5);
    Paint side1_paint = Paint()..color = Color(0xFFafafde);
    Path box_side1 = Path();
    box_side1.moveTo(41.961316,129.06088);
    box_side1.relativeLineTo(17.10525,-7.92423);
    box_side1.relativeLineTo(18.609355,3.37377);
    box_side1.relativeLineTo(-20.451153,6.31617);
    box_side1.close();
    canvas.drawPath(box_side1, side1_paint);

    Paint side2_paint = Paint()..color = Color(0xFFd7d7ff);
    Path box_side2 = Path();
    box_side2.moveTo(59.066566,93.428479);
    box_side2.relativeLineTo(0, 27.708171);
    box_side2.relativeLineTo(18.609355,3.37377);
    box_side2.relativeLineTo(0, -24.29189);
    box_side2.close();
    canvas.drawPath(box_side2, side2_paint);

    Paint side3_paint = Paint()..color = Color(0xFF8686bf);
    Path box_side3 = Path();
    box_side3.moveTo(41.961316,109.37679);
    box_side3.relativeLineTo(17.10525,-15.948311);
    box_side3.relativeLineTo(0, 27.708171);
    box_side3.relativeLineTo(-17.10525,7.92423);
    box_side3.close();
    canvas.drawPath(box_side3, side3_paint);

  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
} 

Dartpad running the 3D Box paintercode

This was a relatively quick world wind tour of the capabilities of CustomPaint, CustomPainter, and the Canvas. The upside of Flutter's canvas is that if you are familiar with OpenGL, HTML5 Canvas, or really any low level drawing API, you'll be fine with it. My samples in this post were a mix of ports from an old Udacity course on libGDX (Java game library), adapting an SVG file, and a bespoke example.

The Dartpad holding all the code is here: https://dartpad.dev/b9553214b6cf88410bff2eb5cc89153c

Planning Your Entry for the Flutter Clock Challenge

Tags: Flutter Programming

Photo by Med Badr Chemmaoui on Unsplash

Officially, I'm ineligible to enter the Flutter Clock Challenge. However, that doesn't bar me from sharing what I would think about if I could enter and what I've learned along the way.

You have just under 2 weeks to get something submitted. Let's start with how I would plan my entry.

Grab some paper for designing things.

I love paper as a initial medium because it's easy:

  • to iterate on designs

  • to share with others during the design phase(no software needed), and,

  • to throw away an idea and restart(little sunken cost fallacy).

Avoiding/solving a problem on paper saves more time down the line than if you jumped into code and had to solve it later.

I'm really intrigued by the design studies the Google Design team used when creating Material Design.

Decide on a concept and sketch out all components you need.

If you are designing a typeface, you'll want to think about how individual elements look in isolation and in combination. If we had months, I'd suggest looking over Thinking with Type by Ellen Lupton. A TL;DR view is to pay attention to kerning, the spacing between character glyphs, to make sure things don't look wonky.

One of the example concepts I was working on looking fine as individual glyphs but were illegible in some combinations.

If you are short on ideas, check to see if you library has the book 100 Ideas that Changed Graphic Design by Steven Heller or anything similar. Go take a stroll through a local museum.

If animating, think about transitions both in number(quantity) and nature(how complicated they might be).

For a digital clock, there are a couple of transitions you'd miss if you just animate 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 0.

For a 12-hour clock, you need transitions for 1 and 5 to 0 and 2 to 1.

Ex. 12:59 -> 01:00

For a 24-hour clock, you need transitions for 2,3, and 5 to 0.

Ex. 23:59 -> 00:00

These combinations give you another opportunity for a sanity check.

Decide on an implementation strategy.

I looked into the following as possible strategies for this challenge.

  • Android Vector Drawables

  • Fonts including color fonts

  • Flare/Rive

  • Native drawing

Using raster images is a possibility I didn't entertain because of scaling and size issues. Though the Lenovo clock has a smallish screen, it wouldn't be fair for every clock to indiscriminately ship a bunch of images. With that out of the way, let's talk a bit more about the options I did seriously consider.

Android Vector Drawable / SVG

AnimatedVectorDrawable and animated SVG aren't currently supported natively or via a package on pub.dev.

For static SVG and some proof of concept VectorDrawable support, there is fluttersvg.

This is a little unfortunate because I have considerable experience creating AVDs.

Fonts

Single color fonts are easy and well supported with many effects like shadows.

Muli-Color fonts(hereafter called simply color fonts) are a different story. At present, there are FOUR competing specs, none of which supported everywhere. See colorfonts.wtf for more details.

Chrome supports two out of the four standards however Flutter doesn't seem to support any of them and always falls back to a single color. It's all a bit moot because the one format Android supports natively uses bitmaps, which aren't great for scaling. Additionally, I couldn't find free tools to create them.

All isn't lost. There is a way to fake a color font by layering components, each in their own color. Stay tuned for an upcoming post explaining this in more detail.

Flare/Rive

I don't have much to say about Rive. It has some great animation support but I'm not skilled in it so it has to wait for another time.

Native drawing

Flutter's drawing infrastructure is built upon Skia, a 2D graphic library used by Chrome, ChromeOS, Mozilla Firefox, Fuschia, and Android to some extent.

Flutter's Canvas class aligns Skia's API giving you access to its raw power. Further, the CustomPaint widget allows you to specify CustomPainters that paint on a canvas given a size. You can even use existing framework provided painters like TextPainter to draw text inside your CustomPainter as opposed to doing it the hard way.

Conclusion

As you read, there are a few considerations to keep in mind when approaching this project not limited to the capabilities of your skills but also those of the target system. Wherever you fall on the experience spectrum, I encourage you to give the Flutter Clock Challenge a try.

Check back soon for more posts that will dive into Painters, fonts, and likely some sketchnote summaries on animation.

Which Flutter widgets you should learn first...according to SCIENCE.

Tags:

A couple months ago, I had the question "Would it be possible to code a Flutter app visually without code?" I got my answer and more. But as it goes with big questions, they often lead to more questions.

Ok, cool, you can generate Flutter code with blocks but making a block per widget and implementing all widgets is unsustainable, you'd have to pick a subset. But what subset of widgets would you pick? More to the point, which widgets are used the most in Flutter apps? How would you even figure it out? Can you even?

I put the idea on ice and when I wasn't expecting it came to me in the shower or a dream: ANTLR. Some androids dream of electric sheep, I dream of Backus-Naur form. ANTLR, Another Tool for Language Recognition, is a tool to generate recognizers and create compiler generators for programming languages. It's one of those things you have for a semester in college and likely promptly forget. Or not. I apologize in advance for the small jump into traditional computer science theory, it's worth it, painless, and I'll be quick.

A Bit of Theory

Languages today are generally defined at their most basic level by a grammar. This grammar will define the basic components of a language in terms of what characters, digits, and constructs may be composed to make other constructs. Take this grammar for instance:

grammar Expr;      
prog:  (expr NEWLINE)* ;
expr:  expr ('*'|'/') expr
    |  expr ('+'|'-') expr
    |  INT
    |  '(' expr ')'
    ;
NEWLINE : [\r\n]+ ;
INT     : [0-9]+ ;

It defines a language that defines a program as one or more expressions, each on their own line. Each expression is either an integer, an expression in parens, or an arithmetic expression. The unquoted * and +, Kleene star and plus respectively, are the operators you might recognize from regular expressions to indicate zero or more and one or more.

For a small enough single input, you could parse it with a regular expression. That becomes unwieldy for something like (5 + (34 * 43 + (15-2 * (42-3 / (6 * 2)))).

Now that we have a grammar, one step that must be done before we can work with the code is to send it through a lexer (or tokenizer). It looks at the code character by character and tries to match it to a rule or as a literal. It defines the WHAT that is in your app. Next, the parser takes that set of tokens and tries to apply the parser rules (the lower case ones), arranging the code into an Abstract Syntax Tree, uncovering any possible syntax errors. The parser is what makes sure you aren't a monkey typing at a keyboard and it all makes sense.

Simple Parse Tree Complex Parse Tree

During this tree composition stage, you can write a listener to inform you when a rule has been entered or exited. Modern programming languages may use this step to in a parser to generate code for whatever syntactic sugar shortcut or @Whatever annotation, your language may have. This is also our stop to jump back into Flutter-ville.

Back to Flutter-ville

Deep in the dark recesses of the Dart SDK is a ANTLR grammar for the language. ANTLR's TestRig utility[the images above and below] allows you to see the AST for a given bit of code. A bit of exploratory poking about in files gave me an idea of where to find things. The parse trees for a Dart files are understandably more complex than Expr.

AST for hello_world.dart Flutter example

The names of the widgets could be found by writing a enterTypeWithParameters rule for my listener and iterating over all the Material widget files. Finding usage of widgets in user code was a bit trickier, needing to listen for identifiers and named argument lists.

class ClassListener(var list: IndexTreeList<Any?>) : Dart2BaseListener() {
    override fun enterTypeWithParameters(ctx: Dart2Parser.TypeWithParametersContext?) {
        val identifier = ctx?.text
        if (identifier != null) {
            list.add(mapOf<String, String>("id" to identifier))
            println(identifier)
        }
    }
}

Intaking all this data left me with a rather messy dataset that would need some cleaning to remove false positives(user created classes) and remove some internal classes that are never meant to be used by end-users. Krangl, a Kotlin library for data analysis similar to dplyr from R, helped me massage things into place. The source files were pulled from some open source Flutter apps and some Google samples.

Before I get to the results, let's do some predictions and fun facts. My prediction going in, because what good experiment lacks a hypothesis, was the following to be the top 5 widgets in no specific order:

  • Text

  • Row

  • Column

  • Container

  • Icon

The largest file was 101KB. Its' parse tree was so big and wide, a whooping 94573 x 4326 pixels that it was illegible and crashed several image viewer apps. Whereas my sample data set could process all files at once, for the big data set, I had to batch process them.

The Results

The top 10 widget frequencies were :

  1. Text

  2. Container

  3. Padding

  4. Column

  5. Icon

  6. Row

  7. SizedBox

  8. Center

  9. Expanded

  10. Scaffold Top 10 Widget Frequencies

Padding was an unexpected but understandable surprise. I also pulled a top 20. The big surprise on that list was seeing InkWell edge out ListView which I had pegged for a top 10 or just outside.

Top 20 Widget Frequencies

I generated some graphs on the widgets in the top ten to show which properties were used most often. No real surprises there for the most part. Apologies for one or two of the labels being so long that they become illegible.

Center Properties Column Properties Container Properties Expanded Properties Icon Properties Padding Properties Row Properties Scaffold Properties SizedBox Properties Text Properties

Last fun note, the top 20 widgets are used 78.21% of the time in my data set. Yay for the Pareto principle(80/20 rule).

Full res versions of the generated charts here: https://github.com/jwill/flutter-analysis/tree/master/pngs

My code is on Github: https://github.com/jwill/flutter-analysis

Have fun, explore, and feel free to tell me what I missed.

Working on document steps to reproduce a bit better but you should be able to put pop it into IDEA/Android Studio, run generateGrammarSource then DartTest and be up and running.