#line 1040 "/home/travis/build/felix-lang/felix/src/packages/gui.fdoc"
  // interim menu stuff
  // these menus are transient, retaining state only when open
  
  
  include "std/datatype/lsexpr";
  
  class FlxGuiMenu
  {
    // A menu entry is either some text or a separator
    // The text has a visual label and a separate tag
    // returned when an entry is selected
    union menu_entry_active_t = Active | Disabled;
    typedef menu_text_entry_t = (tag:string, label:string, active:menu_entry_active_t);
  
    union menu_entry_t = Separator | Text of menu_text_entry_t;
  
    // A menu is a list of trees with both leaves and nodes labelled
    typedef menu_item_t = LS_expr::lsexpr[menu_entry_t, menu_entry_t];
    typedef menu_data_t = list[menu_item_t];
  
    // A position in the tree is a list of integers
    // Separators do not count
    typedef menu_position_t = list[int];
  
    // A menu is either closed, or open at a particular position
    union menu_state_t = Closed | Open of menu_position_t;
  
    union menu_action_t = NoAction | ChangedPosition | SelectedAction of string;
  
    interface menu_model_t
    {
      get_menu: 1 -> menu_data_t;
      get_state: 1 -> menu_state_t;
      set_state: menu_state_t -> 0;
      get_current_tag: 1 -> string; // empty string if closed
      get_current_tag_chain: 1 -> list[string]; // from the top
    }
  
    object MenuModel (m:menu_data_t) implements menu_model_t =
    {
      var state = Closed;
      method fun get_menu () => m;
      method fun get_state () => state;
      method proc set_state (s:menu_state_t) => state = s;
  
      // find ix'th entry in a menu if it exists,
      // separators not counted
      fun find (m:menu_data_t, ix:int) : opt[menu_item_t] =>
        match m with
        | #Empty => None[menu_item_t]
        | Cons (h,t) =>
          match h with
          | Leaf (Separator) => find (t,ix)
          | x => if ix == 0 then Some x else find (t,ix - 1)
          endmatch
        endmatch
      ;
  
      fun find_tag (pos: menu_position_t, menu:menu_data_t) : string =>
        match pos,menu with
        | #Empty, _ => "Empty"
        | Cons (i,t), m =>
          match find (m,i),t with
          | Some (Leaf (Text (tag=tag))), Empty => tag
          | Some (Tree (Text (tag=tag), _)), Empty => tag
          | Some (Tree (_, subtree)), _=> find_tag (t,subtree)
          | _ => "Error"
          endmatch
        endmatch
      ;
      method fun get_current_tag () =>
       match state with
       | #Closed => "Closed"
       | Open pos =>
          find_tag (pos,m)
       endmatch
      ;
      method fun get_current_tag_chain () => Empty[string];
    }
  
    interface menu_display_t
    {
      display: 1 -> 0;
      get_hotrects: 1 -> list[rect_t * menu_position_t];
      get_tag: 1 -> string;
    }
  
    typedef submenu_icon_t = (open_icon: surface_t, closed_icon: surface_t);
  
    object MenuDisplay
    (
      tag:string,
      m:menu_model_t,
      w:window_t,
      x:int,y:int,
      font:font_t,
      text_colour: button_colour_scheme_t,
      disabled_colour: button_colour_scheme_t,
      selected_colour: button_colour_scheme_t,
      submenu_icons: submenu_icon_t
    ) implements menu_display_t =
    {
      method fun get_tag () => tag;
  
      var icon_width = max (submenu_icons.open_icon.get_width(), submenu_icons.closed_icon.get_width());
      var lineskip = get_lineskip font;
      var baseline_offset = font.TTF_FontAscent;
      var border_width = 2;
      var left_padding = 4;
      var right_padding = 10 + icon_width;
      var min_width = 20;
      var separator_depth = 1;
      var top_padding = 1;
      var bottom_padding = 1;
  
      fun width (s:string) => (FlxGuiFont::get_textsize (font,s)).0;
      fun width: menu_entry_t -> int =
        | #Separator => left_padding + right_padding + min_width
        | Text s => left_padding + right_padding + width s.label
      ;
      fun depth : menu_entry_t -> int =
        | #Separator => top_padding + bottom_padding + separator_depth
        | Text s => top_padding + bottom_padding + lineskip
      ;
      fun width : menu_item_t -> int =
        | Leaf menu_entry => width menu_entry
        | Tree (menu_entries ,_) => width menu_entries
      ;
  
      fun depth : menu_item_t -> int =
        | Leaf menu_entry => depth menu_entry
        | Tree (menu_entry ,_) => depth menu_entry
      ;
      fun width (ls: menu_data_t) => fold_left
        (fun (w:int) (menu_item:menu_item_t) => max (w, width menu_item))
        0
        ls
      ;
      fun depth (ls: menu_data_t) => fold_left
        (fun (d:int) (menu_item:menu_item_t) => d + depth menu_item)
        0
        ls
      ;
      proc display_menu(x:int, y:int, menu:menu_data_t, position:menu_position_t)
      {
        var left_x = x;
        var top_y = y;
        var right_x = left_x + width menu;
        var bottom_y = top_y + depth menu;
        var scheme = text_colour;
  
        // 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);
  
        var selected = match position with
          | #Empty => 0 // ignore for the moment
          | Cons (h,_) => h
        ;
  
        var counter = 0;
        var ypos = top_y + top_padding;
        proc show_entry (entry: menu_entry_t) (submenu:menu_data_t) =>
          match entry with
          | #Separator =>
            var y = ypos;
            w.add$ mk_drawable tag draw_line (RGB(0,0,0), left_x, y, right_x, y);
            ypos = ypos + separator_depth + bottom_padding + top_padding;
  
          | Text (label=s,active=active) =>
            y = ypos + baseline_offset;
            var scheme, dosub = match active with
              | #Active => if counter == selected then selected_colour, true else text_colour, false
              | #Disabled => disabled_colour, false
            ;
            var client_area = rect_t (
              left_x+border_width,
              ypos+top_padding,
              right_x - left_x - 2 * border_width,
              lineskip
            );
            w.add$ mk_drawable tag fill (client_area, scheme.bg_colour);
            w.add$ mk_drawable tag FlxGui::write (left_x+left_padding, y,font,scheme.label_colour,s);
  
            match submenu with
            | #Empty => ;
            | _ =>
              var icon = if selected == counter then submenu_icons.open_icon else submenu_icons.closed_icon;
              var dst = rect_t (right_x - icon_width - border_width - 1, ypos, 0,0);
              w.add$ mk_drawable tag blit (dst.x, dst.y, icon.get_sdl_surface());
              if dosub do
                var subpos = match position with
                  | Cons (_,tail) => tail
                  | _ => position // empty
                ;
                display_menu (right_x+border_width,ypos+border_width,submenu,subpos);
              done
            endmatch;
            ypos = ypos + lineskip + bottom_padding+top_padding;
            ++counter;
          endmatch
        ;
        for item in menu do
          match item with
          | Leaf entry => show_entry entry Empty[LS_expr::lsexpr[menu_entry_t, menu_entry_t]];
          | Tree (entry, submenu) => show_entry entry submenu;
          endmatch;
        done
      }
      method proc display() {
        val position = match #(m.get_state) with
          | #Closed => list (0)
          | Open p => p
        ;
        display_menu (x,y,#(m.get_menu), position);
        //w.update();
      }
  
      proc get_hotrecs(x:int, y:int, menu:menu_data_t, position:menu_position_t)
        (revtrail: list[int])
        (photrecs:&list[rect_t * menu_position_t])=
      {
  //println$ "get_hotrecs, revtrail=" + revtrail.str+", pos=" + position.str;
        var left_x = x;
        var top_y = y;
        var right_x = left_x + width menu;
        var bottom_y = top_y + depth menu;
  
        var selected = match position with
          | #Empty => 0 // ignore for the moment
          | Cons (h,_) => h
        ;
  
        var counter = 0;
        var ypos = top_y + top_padding;
        proc hotrecs (entry: menu_entry_t) (submenu:menu_data_t)
        {
          match entry with
          | #Separator =>
            ypos = ypos + separator_depth + bottom_padding + top_padding;
  //println$ "SEPARATOR : Counter="+counter.str;
  
          | Text (label=s,active=active) =>
            y = ypos + baseline_offset;
            var dosub = match active with
              | #Active => counter == selected
              | #Disabled => false
            ;
            var client_area = rect_t (
              left_x+border_width,
              ypos+top_padding,
              right_x - left_x - 2 * border_width,
              lineskip
            );
  //println$ "TEXT: Counter="+counter.str+", Rect=" + client_area.str;
            match active with
            | #Active => photrecs <- (client_area, rev (counter + revtrail)) + *photrecs;
            | #Disabled => ;
            endmatch;
            match submenu with
            | #Empty => ;
            | _ =>
              if dosub do
                var subpos = match position with
                  | Cons (_,tail) => tail
                  | _ => position // empty
                ;
                get_hotrecs (right_x+border_width,ypos+border_width,submenu,subpos) (counter+revtrail) photrecs;
              done
            endmatch;
            ypos = ypos + lineskip + bottom_padding+top_padding;
            ++counter;
          endmatch;
        }
        for item in menu do
          match item with
          | Leaf entry => hotrecs entry Empty[LS_expr::lsexpr[menu_entry_t, menu_entry_t]];
          | Tree (entry, submenu) => hotrecs entry submenu;
          endmatch;
        done
      }
  
      method fun get_hotrects() : list[rect_t * menu_position_t] =
      {
        val position = match #(m.get_state) with
          | #Closed => list (0)
          | Open p => p
        ;
        var hotrecs = Empty[rect_t * menu_position_t];
        get_hotrecs (x,y,#(m.get_menu),position) Empty[int] &hotrecs;
        return rev hotrecs;
      }
  
    }
  
    fun hotpos (point:SDL_Point, hot:list[rect_t * menu_position_t]) : opt[menu_position_t] =>
      match hot with
      | #Empty => None[menu_position_t]
      | Cons ((r,pos),tail) =>
        if point \(\in\) r then Some pos else hotpos (point, tail)
      endmatch
    ;
  
    // ===============================================================================
    object MenuBarDisplay
    (
      tag:string,
      m:menu_model_t,
      w:window_t,
      x:int,y:int,
      font:font_t,
      text_colour: button_colour_scheme_t,
      disabled_colour: button_colour_scheme_t,
      selected_colour: button_colour_scheme_t,
      submenu_icons: submenu_icon_t
    ) implements menu_display_t =
    {
      method fun get_tag() => tag;
      var icon_width = max (submenu_icons.open_icon.get_width(), submenu_icons.closed_icon.get_width());
      var lineskip = get_lineskip font;
      var baseline_offset = font.TTF_FontAscent;
      var border_width = 2;
      var left_padding = 4;
      var right_padding = 4;
      var min_width = 20;
      var separator_width = 1;
      var top_padding = 1;
      var bottom_padding = 1;
      var bar_depth =
        top_padding + bottom_padding + lineskip
      ;
  
      fun width (s:string) => (FlxGuiFont::get_textsize (font,s)).0;
  
      fun width: menu_entry_t -> int =
        | #Separator => left_padding + right_padding + separator_width
        | Text s => left_padding + right_padding + max(min_width, width s.label)
      ;
  
      fun width : menu_item_t -> int =
        | Leaf menu_entry => width menu_entry
        | Tree (menu_entry,_) => width menu_entry
      ;
  
      fun width (ls: menu_data_t) => fold_left
        (fun (w:int) (menu_item:menu_item_t) => w + width menu_item)
        0
        ls
      ;
  
      proc display_menu(x:int, y:int, menu:menu_data_t, position:menu_position_t)
      {
        var left_x = x;
        var top_y = y;
        var right_x = left_x + width menu;
        var bottom_y = top_y + bar_depth;
        var scheme = text_colour;
  
        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);
  
        var selected = match position with
          | #Empty => 0 // ignore for the moment
          | Cons (h,_) => h
        ;
  
        var counter = 0;
        var xpos = left_x + left_padding;
  //println$ "Display Menu "+ tag;
        proc show_entry (entry: menu_entry_t) (submenu:menu_data_t) =>
          match entry with
          | #Separator =>
            w.add$ mk_drawable tag draw_line (RGB(0,0,0), xpos, top_y, xpos, top_y+bar_depth);
            xpos = xpos + separator_width + right_padding + left_padding;
  
          | Text (label=s,active=active) =>
            var scheme, dosub = match active with
              | #Active => if counter == selected then selected_colour, true else text_colour, false
              | #Disabled => disabled_colour, false
            ;
            var item_width =  max (width s, min_width);
            var client_area = rect_t (
              xpos+border_width,
              top_y+top_padding,
              item_width,
              lineskip
            );
            w.add$ mk_drawable tag fill (client_area, scheme.bg_colour);
  //println$ "Menu bar counter=" + counter.str + ", xpos= " + xpos.str + ", text="+s.str;
            w.add$ mk_drawable tag FlxGui::write (
              xpos+left_padding,
              top_y+baseline_offset,
              font,
              scheme.label_colour,
              s
            );
  
            match submenu with
            | #Empty => ;
            | _ =>
              if dosub do
                println "SUBMENU SELECTED";
                var smm = MenuModel ( submenu );
                var smd = MenuDisplay ( tag,
                  smm,
                  w,
                  xpos,bottom_y+border_width,
                  font,
                  text_colour,
                  disabled_colour,
                  selected_colour,
                  submenu_icons
                );
                match position with
                | Cons (_,tail) => smm.set_state (Open tail);
                | _ => ;
                endmatch;
                smd.display();
              done
            endmatch;
            xpos = xpos + item_width + right_padding+left_padding;
            ++counter;
          endmatch
        ;
        for item in menu do
          match item with
          | Leaf entry => show_entry entry Empty[LS_expr::lsexpr[menu_entry_t, menu_entry_t]];
          | Tree (entry, submenu) => show_entry entry submenu;
          endmatch;
        done
      }
  
      method proc display() {
        val position = match #(m.get_state) with
          | #Closed => list (0)
          | Open p => p
        ;
        display_menu (x,y,#(m.get_menu), position);
        //w.update();
      }
      proc get_hotrecs(x:int, y:int, menu:menu_data_t, position:menu_position_t)
        (revtrail: list[int])
        (photrecs:&list[rect_t * menu_position_t])=
      {
  //println$ "get_hotrecs, revtrail=" + revtrail.str+", pos=" + position.str;
        var left_x = x;
        var top_y = y;
        var right_x = left_x + width menu;
        var bottom_y = top_y + bar_depth;
  
        var selected = match position with
          | #Empty => 0 // ignore for the moment
          | Cons (h,_) => h
        ;
  
        var counter = 0;
        var xpos = left_x + left_padding;
        proc hotrecs (entry: menu_entry_t) (submenu:menu_data_t)
        {
          match entry with
          | #Separator =>
            xpos = xpos + separator_width + right_padding + left_padding;
  //println$ "SEPARATOR : Counter="+counter.str;
  
          | Text (label=s,active=active) =>
            var dosub = match active with
              | #Active => counter == selected
              | #Disabled => false
            ;
            var item_width = max (width s, min_width);
            var client_area = rect_t (
              xpos+border_width,
              top_y+top_padding,
              item_width,
              lineskip
            );
  //println$ "TEXT: Counter="+counter.str+", Rect=" + client_area.str;
            match active with
            | #Active => photrecs <- (client_area, rev (counter + revtrail)) + *photrecs;
            | #Disabled => ;
            endmatch;
            match submenu with
            | #Empty => ;
            | _ =>
              if dosub do
                var smm = MenuModel ( submenu );
                var smd = MenuDisplay (tag,
                  smm,
                  w,
                  xpos,bottom_y+border_width,
                  font,
                  text_colour,
                  disabled_colour,
                  selected_colour,
                  submenu_icons
                );
                match position with
                | Cons (_,tail) => smm.set_state (Open tail);
                | _ => ;
                endmatch;
                var shots = smd.get_hotrects();
                shots = map (fun (h:rect_t,pos:menu_position_t) => (h,Cons(counter,pos) )) shots;
                photrecs <- *photrecs + shots;
              done
            endmatch;
            xpos = xpos + item_width + right_padding +left_padding;
            ++counter;
          endmatch;
        }
        for item in menu do
          match item with
          | Leaf entry => hotrecs entry Empty[LS_expr::lsexpr[menu_entry_t, menu_entry_t]];
          | Tree (entry, submenu) => hotrecs entry submenu;
          endmatch;
        done
      }
  
  
      method fun get_hotrects() : list[rect_t * menu_position_t] =
      {
        val position = match #(m.get_state) with
          | #Closed => list (0)
          | Open p => p
        ;
        var hotrecs = Empty[rect_t * menu_position_t];
        get_hotrecs (x,y,#(m.get_menu),position) Empty[int] &hotrecs;
        return rev hotrecs;
      }
  
    }
    // ===============================================================================
  
  
    proc menu_controller
    (
      mm: menu_model_t,
      md: menu_display_t,
      ec: ischannel[event_t],
      response: oschannel[menu_action_t]
    ) ()
    {
      md.display();
      var run = true;
      var e = read ec;
      while run do
        match e.type.SDL_EventType with
        | $(SDL_WINDOWEVENT) =>
          match e.window.event.SDL_WindowEventID with
          | $(SDL_WINDOWEVENT_RESIZED) =>
            md.display();
            write$ response, NoAction;
  
          | _ => write$ response, NoAction;
          endmatch;
  
        | $(SDL_MOUSEMOTION) =>
          var hotrecs = md.get_hotrects();
          //List::iter proc (r:rect_t, pos:menu_position_t) { println$ "Rect=" + r.str + ", Pos=" + pos.str; } hotrecs;
  
          var x,y = e.motion.x,e.motion.y; //int32
          match hotpos ( SDL_Point (x.int,y.int), hotrecs) with
          | #None =>
            write$ response, NoAction;
          | Some pos =>
            println$ "Mouse Move Position " + pos.str;
            match #(mm.get_state) with
            | #Closed =>
              write$ response, ChangedPosition;
            | Open oldpos =>
              if oldpos == pos do
                write$ response, NoAction;
              else
                mm.set_state (Open pos);
                write$ response, ChangedPosition;
              done
            endmatch;
          endmatch;
  
        | $(SDL_MOUSEBUTTONDOWN) =>
          hotrecs = md.get_hotrects();
          x,y = e.button.x,e.button.y; //int32
          match hotpos ( SDL_Point (x.int,y.int), hotrecs) with
          | #None =>
            write$ response, NoAction;
          | Some pos =>
            println$ "Mouse down Position " + pos.str;
            match #(mm.get_state) with
            | #Closed =>
              write$ response, ChangedPosition;
            | Open oldpos =>
              if oldpos == pos do
                write$ response, NoAction;
              else
                mm.set_state (Open pos);
                write$ response, ChangedPosition;
              done
            endmatch;
          endmatch;
  
        | $(SDL_MOUSEBUTTONUP) =>
          hotrecs = md.get_hotrects();
          x,y = e.button.x,e.button.y; //int32
          match hotpos ( SDL_Point (x.int,y.int), hotrecs) with
          | #None =>
            write$ response, NoAction;
          | Some pos =>
            println$ "Mouse up Position " + pos.str;
            match #(mm.get_state) with
            | #Closed =>
              write$ response, ChangedPosition;
            | Open oldpos =>
              if oldpos == pos do
                var selected_tag = #(mm.get_current_tag);
                write$ response, SelectedAction selected_tag;
              else
                mm.set_state (Open pos);
                write$ response, ChangedPosition;
              done
            endmatch;
          endmatch;
  
  
  
        | $(SDL_WINDOWEVENT) when e.window.event == SDL_WINDOWEVENT_LEAVE.uint8  =>
          write$ response, NoAction;
  
        | _ =>
          write$ response, NoAction;
        endmatch;
        e = read ec;
      done
  
    }
  
  }