#line 865 "/home/travis/build/felix-lang/felix/src/packages/gui.fdoc"
  class FlxGuiButton
  {
    union button_state_t =
      | Up       // ready
      | Down     // being clicked
      | Disabled // inactive
      | Mouseover // read and mouse is over
    ;
  
    union button_action_t =
      | NoAction
      | ClickAction of string
    ;
  
    interface button_model_t
    {
      get_state: 1 -> button_state_t;
      set_state: button_state_t -> 0;
      get_tag: 1 -> string;
    }
  
    object ButtonModel
      (var tag: string, init_state:button_state_t)
      implements button_model_t
    =
    {
      var state = init_state;
      method fun get_state() => state;
      method proc set_state (s:button_state_t) => state = s;
      method fun get_tag () => tag;
    }
  
    typedef button_colour_scheme_t =
    (
      label_colour: colour_t,
      bg_colour: colour_t,
      top_colour: colour_t,
      left_colour: colour_t,
      bottom_colour: colour_t,
      right_colour: colour_t
    );
  
    interface button_display_t {
      display: 1 -> 0;
      get_client_rect: 1 -> rect_t;
      get_label : 1 -> string;
      get_tag: 1 -> string;
    }
  
    object ButtonDisplay (b:button_model_t)
    (
      w:window_t, // change to surface later
      font:font_t,
      label:string,
      tag: string, // note: NOT the same as the button's tag!
      up_colour: button_colour_scheme_t,
      down_colour: button_colour_scheme_t,
      disabled_colour: button_colour_scheme_t,
      mouseover_colour: button_colour_scheme_t,
      left_x:int, top_y:int, right_x:int, bottom_y:int,
      origin_x:int, origin_y:int
     )
     implements button_display_t =
     {
       // NOTE: the tag must be unique per button-display on each window.
       // it is used to *remove* the drawing instructions from the window
       // for the previous button state prior to adding new instructions.
       // Dont confuse with the label (which might change per display)
       // or the button state tag (which is not enough if the same button state
       // drives two displays on the same window).
       method fun get_tag () => tag;
  
       method fun get_client_rect () =>
         SDL_Rect
         (
           left_x, top_y, right_x - left_x + 1, bottom_y - top_y + 1
         )
       ;
       method fun get_label () => label;
       method proc display()
       {
        var state = b.get_state ();
        var scheme = match state with
          | #Up => up_colour
          | #Down => down_colour
          | #Disabled => disabled_colour
          | #Mouseover => mouseover_colour
          endmatch
        ;
        w.remove tag;
        // top
        w.add$ mk_drawable tag draw_line (scheme.top_colour, left_x - 2,top_y - 2,right_x + 2, top_y - 2) ;
        w.add$ mk_drawable tag draw_line (scheme.top_colour, left_x - 1,top_y - 1,right_x + 1, top_y - 1);
        // left
        w.add$ mk_drawable tag draw_line (scheme.left_colour, left_x - 2,top_y - 2,left_x - 2, bottom_y + 2);
        w.add$ mk_drawable tag draw_line (scheme.left_colour, left_x - 1,top_y - 1,left_x - 1, bottom_y + 1);
        // right
        w.add$ mk_drawable tag draw_line (scheme.right_colour, right_x + 2,top_y - 2,right_x + 2, bottom_y + 2);
        w.add$ mk_drawable tag draw_line (scheme.right_colour, right_x + 1,top_y - 1,right_x + 1, bottom_y + 1);
        // bottom
        w.add$ mk_drawable tag draw_line (scheme.bottom_colour, left_x - 1,bottom_y + 1,right_x + 1, bottom_y + 1);
        w.add$ mk_drawable tag draw_line (scheme.bottom_colour, left_x - 2,bottom_y + 2,right_x + 2, bottom_y + 2);
  
        w.add$ mk_drawable tag fill(SDL_Rect (left_x, top_y, right_x - left_x + 1, bottom_y - top_y + 1), scheme.bg_colour);
        w.add$ mk_drawable tag FlxGuiSurface::write (origin_x, origin_y, font, scheme.label_colour, label);
      } // draw
      display();
    } //button
  
  proc button_controller
  (
    bm: button_model_t,
    bd: button_display_t,
    ec:ischannel[event_t],
    response:oschannel[button_action_t]
  ) () =
  {
    bd.display();
    var run = true;
    var e = read ec;
    while run do
      match e.type.SDL_EventType with
      | $(SDL_MOUSEMOTION) =>
        var x,y = e.motion.x,e.motion.y; //int32
        if SDL_Point (x.int,y.int) \(\in\) bd.get_client_rect () do
          //println$ "Motion in client rect of button " + bd.get_label();
          match bm.get_state () with
          | #Up => bm.set_state Mouseover; bd.display(); // Enter
          | _ => ;
          endmatch;
        else
          match bm.get_state () with
          | #Mouseover => bm.set_state Up; bd.display(); // Leave
          | #Down => bm.set_state Up; bd.display(); // Leave
          | _ => ;
          endmatch;
        done
        write$ response, NoAction;
  
      | $(SDL_MOUSEBUTTONDOWN) =>
        x,y = e.button.x,e.button.y; //int32
        if SDL_Point (x.int,y.int) \(\in\) bd.get_client_rect () do
          //println$ "Button down in client rect of button " + bd.get_label();
          bm.set_state Down; bd.display();
        done
        write$ response, NoAction;
  
      | $(SDL_MOUSEBUTTONUP) =>
        x,y = e.button.x,e.button.y; //int32
        if SDL_Point (x.int,y.int) \(\in\) bd.get_client_rect () do
          //println$ "Button up in client rect of button " + bd.get_label();
          bm.set_state Mouseover; bd.display();
          write$ response, ClickAction #(bm.get_tag);
        else
          bm.set_state Up; bd.display();
          write$ response, NoAction;
        done
      | $(SDL_WINDOWEVENT) when e.window.event == SDL_WINDOWEVENT_LEAVE.uint8  =>
        bm.set_state Up; bd.display();
        write$ response, NoAction;
  
      | _ =>
        write$ response, NoAction;
      endmatch;
      e = read ec;
    done
  
  }
  
  
  } // class