Basic and Usefull Widgets - Examples

Example 1

Screenshot

Example 1 screenshot

Source code

import buw.*;

/**
 * example 1: form with reset button
 * using: VBox, HBox, TextButton, Input, Label, RadioBox, TextCheckBox & Separator
 */

class Example1 {
  public static function main() {
    new Example1();
  }
  
  var first : Input;
  var last : Input;
  var adult : TextCheckBox;
  var gender : RadioBox;

  function new() {
    //create the vertical box container:
    var form = new VBox(1, -1); //use 100% (1) of the screen width, with alignement set to the left (-1)

    //one can add a separator at the top of the form:
    form.pack(new Separator());

    //create an horizontal (cf false) radio box, with two buttons:
    gender = new RadioBox(false).add(new TextRadioButton("M.")).add(new TextRadioButton("Ms."));
    //the widget is stored in a class field to allow callback functions to access the properties of this object (the selected item)

    //stack the radio box into the vertical container:
    form.pack(gender);

    //create then stack two inputs (with labels):
    first = new Input(20); //by default, inputs use 100% of available width
    last = new Input(20);
    form.pack(new Label("Firstname:"));
    form.pack(first);
    form.pack(new Label("Lastname:"));
    form.pack(last);

    //create and stack a check box:
    adult = new TextCheckBox("I am over 18");
    form.pack(adult);

    form.pack(new Separator());

    //add two buttons in an horizontal box:
    var buttons = new HBox(); //by default, the horizontal box will use 100% of the width of its parent
    buttons.pack(new TextButton(onOkClick, "OK", 0.5)); //will use 50% (0.5) of the horizontal box width
    buttons.pack(new TextButton(onResetClick, "Reset", 0.5));
    form.pack(buttons);

    //display the form (vertical box)
    Screen.display(form);
  }

  //the callbacks function receive the clicked widget as parameter:
  function onOkClick(w : TextButton) {
    var prefix : String;
    if (gender.selected == 0) { //access radio box selected item (integer, [0..n-1])
      prefix = "Mister ";
    } else {
      prefix = "Misses ";
    }
    trace(prefix + first.value + " " + last.value); //access inputs values
    if (adult.checked) { //access check box checked value (boolean)
      trace("Ok, You are over 18.");
    }
  }

  function onResetClick(w : TextButton) {
    first.value = ""; //change (reset) input value
    last.value = "";
    adult.checked = false; //change check box state
    gender.selected = 0; //change radio box selected item
  }
}

Example 2

Screenshot

Example 2 screenshot

Source code

import buw.*;

/**
 * example 2: form in a grid (aligned controls)
 * using: Grid (HBoxColumn), TextButton, Input, Label, PasswordInput, Separator & SpinBox
 */

class Example2 {
  public static function main() {
    new Example2();
  }
  
  var first : Input;
  var last : Input;
  var age : TextSpinBox;
  var password : PasswordInput;
  var combo : ComboBox<Book>;

  function new() {
    //this time, the form is a grid with two columns; it is a usefull container for widgets alignement;
    //the first column uses 30% of screen width and is left aligned (cf -1),
    //the second column uses 70% of the screen width (and centered by default):
    var form = new Grid([new HBoxColumn(0.3, -1), new HBoxColumn(0.7)]);
    
    //by default, inputs use 100% of available width (70% of screen width, since they are put in the second column)
    first = new Input(20); 
    last = new Input(20);
    
    //widgets are stacked line by line, from left to right:
    form.pack(new Label("Firstname:"));
    form.pack(first);
    //third packing is for the second line (since there are two columns):
    form.pack(new Label("Lastname:"));
    form.pack(last);
    
    age = new TextSpinBox("yo", 18, 1, 99); //label, value, min, max
    age.value = 100; //to demonstrate how to change spinbox value; will be set to 50 because 100 is off limits
    form.pack(age);
    form.pack(new Separator());
    
    password = new PasswordInput("", 20);
    form.pack(new Label("Password:"));
    form.pack(password);
    
    form.pack(new Separator());
    form.pack(new TextButton(onClick, "OK"));
    Screen.display(form);
  }

  function onClick(w : TextButton) {
    trace(first.value + " " + last.value + " " + age.value + "yo; password=" + password.value); //access inputs and spinboxes values
  }
}

Example 3a

Screenshot

Example 3a screenshot

Source code

import buw.*;

/**
 * example 3a: list view without renderer (using toString)
 * using: VBox, ListView, Separator, Label & TextButton
 */

class Book {
  public var author : String;
  public var title : String;
  public function new(t : String, a : String) {
    author = a;
    title = t;
  }
  
  //the toString method is required to render list view items (when no renderer is provided)
  public function toString() : String {
    return title + " - " + author;
  }
}

class Example3a {
  public static function main() {
    new Example3a();
  }
  
  //a list view is a usefull widget to display collections of items; it is strongly typed:
  var booksList : ListView<Book>;

  function new() {
    var rootWindow = new VBox();
    rootWindow.pack(new TextButton(onAddBooksClick, "Add books"));
    Screen.display(rootWindow); //enables vertical scrolling if needed
    rootWindow.pack(new Separator());

    //because a Book has a toString method, no renderer is required; one can provide a callback to tell what to do when an item is clicked:
    booksList = new ListView(onBookClick);
    rootWindow.pack(booksList);
  }

  function onAddBooksClick(w : TextButton) {
    //list views source can be arrays or lists:
    var books : Array<Book> = new Array();
    books.push(new Book("Les misérables", "Victor Hugo"));
    books.push(new Book("20 000 lieues sous les mers", "Jules Verne"));
    
    //first time the button is clicked, source is set:
    if (booksList.source == null) {
      booksList.source = books;
    } else {
      //then, for demonstration purpose, the same books are added to the existing source:
      for (b in books) {
        booksList.push(b);
      }
    }
  }

  //the callback function is given the clicked item as parameter:
  function onBookClick(b : Book) {
    trace(b.title);
  }
}

Example 3b

Screenshot

Example 3b screenshot

Source code

import buw.*;

/**
 * example 3b: list view using renderer
 * using: VBox, ListView, Separator, Label & TextButton
 */

typedef Book = {
  var author : String;
  var title : String;
  
  //no required toString method this time
}

class Example3b {
  public static function main() {
    new Example3b();
  }
  
  var booksList : ListView<Book>;

  function new() {
    var rootWindow = new VBox();
    rootWindow.pack(new TextButton(onAddBooksClick, "Add books"));
    Screen.display(rootWindow);
    rootWindow.pack(new Separator());

    //to create a list view, one must tell how to render an item, and eventually tell what to do when an item is clicked:
    booksList = new ListView(renderBook, onBookClick);
    rootWindow.pack(booksList);
  }

  function onAddBooksClick(w : TextButton) {
    var books : Array<Book> = new Array();
    books.push({title : "Les misérables", author : "Victor Hugo"});
    books.push({title : "20 000 lieues sous les mers", author : "Jules Verne"});
    booksList.source = books;
  }

  //the render function is given an item as a parameter; the returned widget will be displayed by the list view;
  //this function is called once per widget in the item collection:
  function renderBook (b : Book) : Widget {
    //the returned widget is here a label:
    return new Label(b.title + " - " + b.author);
    //it could be something more complex like an horizontal box with an image and a label for example
  }

  function onBookClick(b : Book) {
    trace(b.title);
  }
}

Example 4

Screenshot

Example 4 screenshot

Source code

import buw.*;

/**
 * example 3: table view
 * using: Table (TableColumn), Image, HBox & Label
 */

typedef Book = {
  var author : String;
  var title : String;
  var alive : Bool;
}

class Example4 {
  public static function main() {
    new Example4();
  }
  
  function new() {
    //in this example, a collection of books is displayed in a table;
    //a table has columns that have a (relative) width, a title and the field name to display or a rendering function for the cell:
    var booksTable : Table<Book> = new Table(
      function (o : Book) { trace(o.title); }, 
      [
        new TableColumn(0.6, "Title", "title"), //for the first colum, the book title is displayed (in a Label)
        new TableColumn(0.4, "Author", renderAuthorColumn) //for the second one, the cell is rendered by a function
      ]);
    Screen.display(booksTable);

    var books : Array<Book> = new Array();
    books.push({title : "La Horde du contrevent", author : "Alain Damasio", alive : true});
    books.push({title : "La fraternité du Panca", author : "Pierre Bordage", alive : true});
    books.push({title : "Ravage", author : "René Barjavel", alive : false});
    
    booksTable.source = books;
  }

  function renderAuthorColumn (b : Book) : Widget {
    //the author column is rendered in an horizontal box:
    var hbox = new HBox();
    //containing the authors name:
    hbox.pack(new Label(b.author));
    if (! b.alive) {
      //and an image if dead:
      hbox.pack(new Image("assets/rip.jpg", Widget.controlsFontSize));
    }
    return hbox;
  }
}

Example 5

Screenshot

Example 5 screenshot

Source code

import buw.*;

/**
 * example 5: querying a web service
 * using: VBox, ListView, Separator, Label, TextButton & Input
 * nothing new with BUW here, it is only a demonstration of its interoperability with haxe.Http class
 */

typedef Result = {
  var type : String;
  var display_name : String;
  var lat : Float;
  var lon : Float;
}

class Example5 {
  public static function main() {
    new Example5();
  }
  
  static inline var WSURI : String = "https://nominatim.openstreetmap.org/search?format=json&q=";
  var search : Input;
  var resultsList : ListView<Result>;

  function new() {
    var rootWindow = new VBox();
    rootWindow.pack(new Label("Enter search location:"));
    search = new Input(40);
    rootWindow.pack(search);
    rootWindow.pack(new TextButton(onSearchClick, "Search"));
    Screen.display(rootWindow);
    rootWindow.pack(new Separator());
    resultsList = new ListView(renderSearch, onResultClick);
    rootWindow.pack(resultsList);
  }

  function onSearchClick(w : TextButton) {
    if (search.value != "") {
      var httpRequest = new haxe.Http(WSURI + search.value);
      httpRequest.onError = function (msg : String) {
        trace(msg);
      }
      httpRequest.onData = function (jsonData : String) {
        var results : List<Result> = Lambda.filter(haxe.Json.parse(jsonData), 
                        function (r: Result) { return r.type == "city"; });
        Lambda.iter(results, function (r : Result) {
          var parts : Array<String> = r.display_name.split(", ");
          r.display_name = parts[0] + ", " + parts[2];
        });
        resultsList.source = results;
      }
      httpRequest.addHeader("User-Agent", "haxe.tuxfamily.org");
      httpRequest.request();
    }
  }

  function renderSearch (r : Result) : Widget {
    return new Label(r.display_name);
  }

  function onResultClick(r : Result) {
    trace(r.type);
  }
}

Example 6

Screenshot

Example 6 screenshot

Source code

import buw.*;
import openfl.Lib;
import openfl.events.Event;
//~ import openfl.events.KeyboardEvent;
import openfl.display.Sprite;

/**
 * example 6: list view with detail, displaying both (in an horizontal pane) when screen Width > 600px
 * using: HBox, ListView, VBox, Label, TextButton, Paragraph
 */

class Book {
  public var author : String;
  public var title : String;
  public var synopsis : String;
  public function new(t : String, a : String, s : String) {
    author = a;
    title = t;
    synopsis = s;
  }
  
  public function toString() : String {
    return title;
  }
}

class Example6 extends Sprite {
  static inline var LIST_WIDTH : Float = 0.3; //relative
  static inline var MIN_WIDTH : Int = 600; //pixels
  
  var booksList : ListView<Book>;
  var bookDetail : VBox;
  
  var currentScreen : Widget; //booksList xor bookDetail
    //used (displayed) when screen width < 600px
  
  var hpane : HBox; //the horizontal pane
    //displayed when screen width >= 600px
    //an horizontal box with 2 children: booksList (30%) & booksDetail (70%)

  function new() {
    super();
    
    //first initialize the widgets:
    var books : Array<Book> = new Array();
    for (i in 0...25) { //to demonstrate scrolling on the list
      books.push(new Book("Ravage", "René Barjavel", "Ravage présente le naufrage d`une société mature, dans laquelle, un jour, l`électricité disparaît et plus aucune machine ne peut fonctionner. Les habitants, anéantis par la soudaineté de la catastrophe, sombrent dans le chaos, privés d`eau courante, de lumière et de moyens de déplacement."));
      books.push(new Book("Demain les chiens (City)", "Clifford D. Simak", "Sur plusieurs milliers d`années, l`auteur brosse un tableau troublant et poétique de l`avenir de l`humanité. Les Hommes perdent petit à petit leur instinct grégaire après une série de conflits guerriers et font un retour dans les campagnes, puis laissent la place aux Chiens, qu`ils ont réussi à doter de la parole."));
    }
    booksList = new ListView(onBookClick);
    booksList.source = books;
    
    bookDetail = new VBox(); //empty by default
    hpane = new HBox([new HBoxColumn(LIST_WIDTH), new HBoxColumn(1-LIST_WIDTH, -1)]); //two columns
    currentScreen = booksList; //default is to display the list
    
    //listen to resize event to switch between single / dual (horizontal pane) view
    Lib.current.stage.addEventListener(Event.RESIZE, onResize); 
    //~ Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); //to handle escape key
    draw();
  }
  
  function onResize(e : Event) {
    draw();
  }
  
  function draw() {
    hpane.clear(); //remove list and detail view from horizontal box to use full width when < 600px
    if (Lib.current.stage.stageWidth >= MIN_WIDTH) {
      //add list view and detail widget to the horizontal box and display it:
      hpane.pack(booksList);
      hpane.pack(bookDetail);
      Screen.display(hpane);
      Screen.setScrollable(booksList); //only the list view is scrollable
      
      //draw a separator line between list and detail:
      hpane.graphics.clear();
      hpane.graphics.lineStyle(Widget.borderWidth);
      hpane.graphics.drawRect(Lib.current.stage.stageWidth * LIST_WIDTH, 0, 1, Lib.current.stage.stageHeight);
      hpane.graphics.endFill();
    } else { //display only list or detail:
      Screen.display(currentScreen);
    }
  }

  function onBookClick(b : Book) { //display detail
    bookDetail.clear();
    bookDetail.pack(new Title("Title:"));
    bookDetail.pack(new Label(b.title));
    bookDetail.pack(new Title("Author:"));
    bookDetail.pack(new Label(b.author));
    bookDetail.pack(new Title("Synopsis:"));
    bookDetail.pack(new Paragraph(b.synopsis));
    bookDetail.pack(new TextButton(onBackClick, "Back"));
    currentScreen = bookDetail;
    draw();
  }
  
  function onBackClick(w : TextButton) { //display list
    bookDetail.clear();
    currentScreen = booksList;
    draw();
    booksList.draw(); //needed if width has changed while displaying detail
  }
  
  //~ function onKeyUp(e : KeyboardEvent) {
    //~ if (e.keyCode == 27) {
      //~ onBackClick(null);
    //~ }
  //~ }
}