Script Library: 1240 scripts
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

Archive version of: menu-system.r ... version: 1 ... christian 9-Jun-2005

Amendment note: new script || Publicly available? Yes

REBOL [
    Title:   "Menu-System"
    Name:    'Menu-System
    File:    %menu-system.r
    
    Version: 0.1.6
    Date:    10-Jun-2005 
        
    Author: "Christian Ensel"
    Email:  christian.ensel@gmx.de
    
    Owner:  "Christian Ensel"
    Rights: "Copyright (c)2005 Christian Ensel"                                 
   
    Purpose: {
        Pre-Alpha of a pretty complete REBOL menu system.
        Have menus in your REBOL apps, finally.
    }
    
    History: {
        0.1.6 • Shortcut keys are now dialected as TAG! instead of ISSUE!,
                TAG! is way more flexible.
              • Styling with LAYOUT-MENU/STYLE works.
              • Styling MENU-BAR and DROP-MENU with MENU-STYLE works.
              • Correction of layout algorithm, still somewhat problematic.
                            
        0.1.5 • MENU-BAR style now with full keyboard support, but still
                "menubar" and "baritems" aren't configurable.
              • DROP-MENU now works again.
        0.1.4 • Experimental MENU-BAR VID style now works.
                It isn't configurable much and there are some really annoying bugs.
              • Drop-Menu is broken for now.
        0.1.3 • Experimental DROP-MENU VID-style.
        0.1.2 • Dialect changes.
        0.1.1 • Dialect changes.
        0.1.0 • Refactored earlier prototype.
    }
    
    Credits: {
        Originally this script evolved from trying to understand the inner
        workings of Cyphre's menu system sketch, without that I would by
        no means have come as far as shown here.
    }
    
    Library: [
        level:      'intermediate
        platform:   'all
        type: [module demo]
        code: 'module
        domain: [user-interface vid gui]
        tested-under: [view 1.2.119.3.1 on "WinXP"]
        support: none
        license: none                                                           ;-- Not bothered with licensing stuff yet, but most likely BSD
        see-also: none
    ]
]

ctx-menus: context [

    shadow-image: use [reset image] [
        reset: system/options/binary-base
        system/options/binary-base: 64
        image: load 64#{
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+g
vaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QQUCzYGyPaalQAAAINJ
REFUeNrd0qEOwjAUBdADDIJDIYaZWPjJfSZuAo2bQiybKKZLmqUzRcE1TZO+U3Ef
P59DwUyHCwKGqgBocFou+wLghhptKVDjjGMpkOb6LeAPgCqzJE1SVS4vjJgR1sAj
WZJhAxgj8sS0Bvp4tkvPmcxxeEC/yzy4Lx1vAAFT/Oz9ASj/FhDibXHbAAAAAElF
TkSuQmCC
}
        system/options/binary-base: reset
        image
    ]
    
    ;======================================================= helper functions ==
    ;
    
    cast: func [value cases] [switch type?/word :value cases]

    text-size?: func ["Returns a face's text size or 0x0." face /local size result] [ 
        size: face/size
        face/size: 10000x10000                                                  
        result: size-text face
        face/size: size
        result
    ]
    
    image-size?: func ["Returns a face's image size or 0x0." face [object!]] [
        any [all [face/image face/image/size] 0x0]
    ]
    
    ;=========================================================== detail-faces ==
    ;
    ;   Detail faces are the inner faces of menu-item, each item uses five
    ;   of them: 
    ;
    ;       - MARK:    The radio- or check-mark
    ;       - ICON:    The item's icon image
    ;       - BODY:    The item's body text and/or image
    ;       - KEY:     The item's shortcut-key text
    ;       - ARROW:   The item's arrow (only for item's with sub-menu)  
    ;
    detail-face: make system/view/vid/vid-face [                                ;-- Notice that details have no feel, all item actions are triggered by
        edge: color: effect: effects: none                                      ;   item's feel/detect.
        offset: size: 0x0
        feel: make face/feel [
            redraw: func [detail offset /local item] [
                item: detail/parent-face
                if detail/effects [
                    detail/effect: pick detail/effects item <> item/menu/actor
                ] 
            ]
        ]
    ]
    
    ;============================================================= draw-radio ==
    ;    
       
    ;view layout [box 100x100 effect [draw [push [translate 50x50 pen black box 10x10 20x20]]]]
    
    draw-radio: func [item /local color y] [
        color: item/options/key/font/color 
        item/mark/effect: compose/deep: [
            draw [
                push [
                    pen (color) fill-pen (color) 
                    circle (as-pair 7 item/mark/size/y / 2 - 1) 3
                ]
            ]
        ]
    ]
    ;---------------------------------------------------------------------------
    
    
    ;============================================================= draw-mark ==
    ;    
    draw-mark: func [item /local color unit] [
        color: item/options/key/font/color 
        unit:  item/mark/size / 16x16
        item/mark/effect: compose/deep: [
            draw [
                pen (none) fill-pen (color) anti-alias (none)
                polygon
                    (3x9 * unit) (4x8 * unit) (6x10 * unit)
                    (11x5 * unit) (12x6 * unit) (6x12 * unit)
            ]
        ]
        item/mark/effect: compose/deep: [
            draw [
                pen black line-width 2 line-cap square line-join bevel fill-pen black ;anti-alias (none)
                line (3x8 * unit) (6x11 * unit) (12x5 * unit)
            ]
        ]
    ]
    ;---------------------------------------------------------------------------        
    
    
    ;============================================================= draw-arrow ==
    ;    
    draw-arrow: func [item /local color y] [
        color: item/options/key/font/color 
        item/arrow/effect: compose/deep [
            draw [
                pen (color) fill-pen (color)
                polygon
                    (as-pair item/arrow/size/x -  8 item/arrow/size/y / 2 - 1)
                    (as-pair item/arrow/size/x - 11 item/arrow/size/y / 2 - 4)
                    (as-pair item/arrow/size/x - 11 item/arrow/size/y / 2 + 2)
                    (as-pair item/arrow/size/x -  8 item/arrow/size/y / 2 - 1)
            ]
        ]
    ]
    ;---------------------------------------------------------------------------
    
    
    ;============================================================== draw-knob ==
    ;    
    draw-knob: func [menu dir /local knob color y] [
        knob: select reduce ['less menu/less 'more menu/more] dir
        color: either menu/options/item/body/font/colors [
            menu/options/item/body/font/colors/1
        ][
            menu/options/item/body/font/color
        ]
        y: pick [[5 8] [8 5]] dir = 'less
        knob/effect: compose/deep [
            draw [
                pen (color) fill-pen (color)
                polygon
                    (as-pair knob/size/x / 2     y/1)
                    (as-pair knob/size/x / 2 - 3 y/2)
                    (as-pair knob/size/x / 2 + 3 y/2)
                    (as-pair knob/size/x / 2     y/1)
            ]
        ]
    ]
    ;---------------------------------------------------------------------------
    
   
    ;============================================================== knob-feel ==
    ;
    knob-feel: make face/feel [
	    over: func [knob over? offset] [
            knob/rate: all [over? knob/parent-face/parent-face/options/menu/rate]
            show knob                                                                                 ;************ Is this necessary? ***************
        ]
        engage: func [knob action event /local menu] [
            if event/type = 'time [
                menu: knob/parent-face/parent-face
                menu/feel/scroll menu knob
            ]
        ]
    ]
    
    
    ;============================================================== item-feel ==
    ;
    ;   Initiating drawing the AWAY state of an item is exclusivly
    ;   done by one of it's siblings; i.e. whenever a sibling is
    ;   hovered, sibling forces the active item to unhover and
    ;   then becomes the active item itself.
    ;
    ;   This is used to differentiate between wandering through menus with mouse and 
    ;   keyboard. Whilst hovering items with sub-menus with the mouse, sub-menus are
    ;   opened immediatly, whereas when navigating a menu with the keyboard, that doesn't
    ;   open sub-menus (to not steal the keyboard focus).    
    ;
    
    item-feel: make face/feel [
        
        detect: func [item event] [
            if within? event/offset win-offset? item item/size [
                item/menu/feel/visit item/menu item
                item/feel/enter item
                event
            ]
        ]
        
        redraw: func [
            "Draws item (cares for left, visited and disabled state)." 
            item [object!]
            offset [pair!]
        /local
            active? color details
        ][
            active?: item = item/menu/actor
            
            details: item/pane
            
            either item/state [
                if item/mark <> item/pane/1 [insert item/pane item/mark]
            ][
                if item/mark = item/pane/1 [remove item/pane]
            ]
            
            item/color: either active? [item/options/highlight] [item/options/color]

            if item/options/icons [item/icon/image: pick item/options/icons not active?]
            
            item/body/font/color: any [
                all [in item/body/font 'colors pick item/body/font/colors not active?]
            ]

            if item/edge [
                if in item/edge 'colors [
                    item/edge/color: pick item/edge/colors not active?
                ]
                if in item/edge 'effects [
                     item/edge/effect: pick reduce item/edge/effects not active?
                ]
            ]
            
            either item/disabled? [                                             ;-- Hardcoded DISABLE state here. Has to be dialected!
                item/body/font/color: silver
                either item/icon/effect [
                    if not find item/icon/effect 'grayscale [
                        insert tail item/icon/effect [grayscale luma 25]
                    ]
                ][
                    item/icon/effect: copy [grayscale luma 25]
                ]
            ][
                if item/icon/effect [remove/part any [find item/icon/effect 'grayscale []] 3]
            ]

        ]
    
        visit: func [                                                     
            "Visits item (draws it's hover state)."
            item [object!]
        ][
            show item                                                           ;-- Show that we're visited
        ]
        
        enter: func [
            "Enters item (shows it's sub-menu)."
            item [object!]
        /new
            "Returns immediately from showing."
        ][
            if all [                                                            ;-- Show the own sub-menu, if there's one and if it's not
                item/sub                                                        ;   popped up already 
                none? find system/view/pop-list item/sub
            ][ 
                either new [
                    show-menu/new find-window item item/sub                     ;-- Keyboard entered sub-menu
                ][
                    show-menu     find-window item item/sub                     ;-- Mouse opened sub-menu
                ]
            ] 
        ]
        
        leave: func [                                                           ;-- What's normal, anyway? Normal can be disabled, REDRAW cares for that.
            "Leaves item (draws it's normal state and closes it's sub-menu)."   ;   
            item [object!]                                                      ;
        ][                                                                      ;   
            all [item/sub item/sub/feel/close item/sub]
            show item
        ] 
        
        engage: func [
            item action event 
            /local root popup start end items state
        ][
            
            either all [action = 'up none? item/sub not item/disabled?] [           ;-- We act only on 'UP events for nothing but enabled items without sub-menus.                                                     
                all [
                    logic? item/state 
                    case [
                        'radio = item/mark/type [
                            if off = item/state [
                                item/state: true
                                                
                                items: item/menu/list/pane
                                foreach sibling items [
                                    if all [
                                        sibling <> item
                                        item/group = get in sibling 'group
                                    ][
                                        sibling/state: false
                                        show sibling/mark
                                    ]
                                ]
                                        
                            ] 
                        ]
                        'check = item/mark/type [
                            item/state: not item/state
                        ]
                    ]
                ]        
                
                either not event/shift [                                        ;-- SHIFT enables multi-selection, otherwise the whole menu closes.
                    root: item/menu/feel/root-of item/menu
                    root/feel/close root
                ][
                    show item
                ]
                
                do func [item] bind any [item/options/action []] in item 'self item   ;-- Should I allow for functions here, too?
               
            ][
                ;-- no op
            ]
            
            none
        ]
    ]
    
    ;=========================================================== baritem-feel ==
    ;
    
    baritem-feel: make item-feel [
        super: item-feel
        over: none 
        engage: none
        redraw: func [item offset] [
            item/color: pick item/menu/colors item <> item/menu/actor    
        ]
        detect: func [item event /local actor] [
            case [
                none? item/menu/actor [
                    item/menu/state: 'down-to-enter
                    item/menu/feel/visit item/menu item
                    focus/no-show item/menu
                ]
                'down-to-enter = item/menu/state [
                    item/menu/feel/visit item/menu item
                    focus/no-show item/menu
                    if 'down = event/type [
                        item/menu/state: 'hover-to-enter
                        item/feel/enter item
                    ]
                ]
                'hover-to-enter = item/menu/state [
                    if 'move = event/type [ 
                        item/menu/feel/visit item/menu item
                        item/feel/enter item
                    ]
                ]
            ]
            none
        ]
        enter: func [
            "Enters item (shows it's sub-menu)."
            item [object!]
        /new
            "Returns immediately from showing."
        ][
            if all [                                                            ;-- Show the own sub-menu, if there's one and if it's not
                item/sub                                                        ;   popped up already 
                none? find system/view/pop-list item/sub
            ][ 
                either new [
                    show-menu/offset/new find-window item/menu item/sub item/offset + (0x1 * item/size)                     ;-- Keyboard entered sub-menu
                ][
                    show-menu/offset find-window item/menu item/sub item/offset + (0x1 * item/size)                    ;-- Mouse opened sub-menu
                ]
            ] 
        ]                                            
    ]
    
    ;============================================================= build-item ==
    ;
    build-item: func [
        "Builds an item, together with all it's sub-faces."
        parent [object!] "the item's menu"
        word   [word! none!] "the item's identifier"
        desc   [block!]  "an items description"
    /local
        item
    ][     
        item: make face [
            
            type:   'menu-item
            
            var:     word                                                        ;-- VAR holds the word by which we later refer to the item.
            
            menu:   parent                                                      ;-- MENU is always the menu the item belongs to, it is *not* the 
                                                                                ;   sub-menu an item may have (thats in SUB).
            
            color: colors: none                                                 ;-- COLORS allows for multiple activation-state dependend colors 
                                                                                ;   (normal, actor, disabled).        
                                                                                
            effect: effects: none                                               ;-- EFFECTS allows for multiple activation-state dependend effects
                                                                                ;   (normal, actor, disabled).    
            
            highlight: none                                                     ;-- I have to get rid of that in favour of COLORS.
            
            
            text: none                                                          ;-- TEXT is actually BODY/TEXT, REDRAW changes this on-the-fly.
            font: none                                                          ;   Whereas FONT is unused; BODY/FONT is OPTIONS/BODY/FONT.
            
            image: images: none                                                 ;-- Same with IMAGE, which is drawn as BODY/IMAGE.
                                                                                ;   IMAGES allows for multiple activation-state dependend images 
                                                                                ;   (normal, actor, disabled).
            
            shortcut: none                                                      ;-- And dito here: SHORTCUT is KEY/TEXT.
                                                                                ;   BTW, how's about having images for keys? Can think of e.g. shift-arrow ...
                                                                                ;   Would be fun implementing that.
            
            options: none                                                       ;-- All the item related options.
                                                                                ;   I'm concerned with this being an OBJECT!, whilst VID usually keeps a BLOCK! here.
            
            feel: item-feel                                                     ;-- See detailed comments on ITEM-FEEL.
            
            edge: make face/edge [size: 0x0 color: image: effect: none]         ;## I'm not satisfied with having that here, should rather be an option.
            edges: none
            
            data: desc                                                          ;-- The item's sub-menu's description, if any.
            sub: none                                                           ;-- The item's sub-menu object itself, if any.
            
            disabled?: no                                                       ;-- The enabled/disabled state.
            
            state: none                                                         ;-- Toggles the radio- or check-mark state.

            group: none                                                         ;-- GROUP is used for mark-items. Each menu starts which one initial group,
                                                                                ;   implicit groups are set up by menu-dividers and additional, more complex
                                                                                ;   groups can be explicitly specified in the setup dialect. 
            

            mark: icon: body: key: arrow: none                                  ;-- Just some shortcuts to the details in item's pane. 
            
            pane: reduce [
                mark:  make detail-face []
                icon:  make detail-face []
                body:  make detail-face [para: parent/para]
                key:   make detail-face [para: parent/para]
                arrow: make detail-face []
            ]
        
        ]
        item/options: make object! [                                            ;## ALL THIS OPTION STUFF IS REALLY, REALLY CONFUSING. MAP OUT A WORKING STRATEGY! #####
            color:     parent/options/item/color
            colors:    none
            highlight: parent/options/item/highlight
            image:     none
            action:    parent/options/item/action
            icons:     none
            body:      make object! [
                font: parent/options/item/body/font
                para: parent/options/item/body/para
            ]
            key:       make object! [font: parent/options/item/key/font]
            edge:      parent/options/item/edge 
        ]
        item/body/font: item/options/body/font
        item/body/para: item/options/body/para
        item/key/font:  item/options/key/font
        item/edge:      item/options/edge
        
        item
    ]
    
    
    ;========================================================== build-divider ==
    ;
    ;   DIVIDER needs to be reworked, I guess. Currently, there are absolutely
    ;   no config options.
    ;
    build-divider: func [
        "Builds a menu divider."        
        parent [object!] "the divider's parent menu"
    ][     
        make face [
            type: 'menu-divider
            var: none
            menu: parent
            color: 172.168.153                                                  ;-- Win XP Silver; hardcoded for now
            edge: make face/edge [                                              ;-- Ditto, look like Win XP Silver.
                size:  1x2
                color: image: effect: none
            ]
        ]
    ]
    ;---------------------------------------------------------------------------
    
    
    ;============================================================== menu-feel ==
    ;
    ;   The MENU-FEEL is used to the support keyboard-handling solely,
    ;   all mouse related behaviour is done one ITEM-FEEL level.
    ;
    ;   WARNING: I don't care about not selecting DIVIDER-ITEMS for
    ;            HOME and END keys, when wrapping with UP and DOWN and
    ;            when entering a sub-menu; so, for now, better not let 
    ;            a DIVIDER-ITEM be the first or last item in a menu 
    ;            (looks ugly, anyway ;-)
    ;
    menu-feel: make system/view/popface-feel-win-away [
        
        super: none
        
        inside-menu?: func [menu event] [
            within? event/offset win-offset? menu menu/size
        ]
        
        inside-menu-tree?: func [menu event] [
            any [
                menu/feel/inside-menu? menu event
                all [
                    menu/parent
                    menu/feel/inside-menu-tree? menu/parent/menu event
                ]
            ]
        ]
        
        pop-detect: func [menu event] [
            case [
                menu/feel/inside-menu-tree? menu event [
                    if find [down up move time key alt-down alt-up scroll-line] event/type [
                        event
                    ]
                ]
                true [
                    either not find [up move time scroll-line key] event/type [
                        menu: menu/feel/root-of menu
                        menu/feel/close menu
                    ][
                        event
                    ]
                ]
            ]
        ] 

        close: func [                                                           ;-- CLOSE actually impements a custom HIDE-POPUP, if you like, call it a hack.
            "Closes a menu (and all of it's items' sub-menus)."                 ;   I simply couldn't cope with that one.
            menu [object!]
        /local                                                                  ;-- NO-SHOW hinders multiple window refreshing closing nested menus. 
            /no-show                                                            ;   Hidden, callers shouldn't have to care about that.                                                                     
        ][
            if all [menu/actor menu/actor/sub] [
                menu/actor/sub/feel/close/no-show menu/actor/sub                ;-- MENU/ACTOR/SUB may have a different FEEL
                                                                                ;   (even though, for now, none are implemented).
            ]
            if find system/view/pop-list menu [
                unfocus menu                                                                                                     ;   
                remove find menu/parent-face/pane menu                          ;-- Most likely to be a window, but I hope it can also be another non-menu popup :-)    
                remove find system/view/pop-list menu
                menu/actor: none
                unless no-show [show menu/parent-face]
            ]
            if menu/parent [focus/no-show menu/parent/menu]
        ]
        
        visit: func [                                                           ;-- Visiting items may require revealing these items, which definitly is 
            "Visits menu item, making it the new actor."                        ;   a job under responsibility of menus. 
            menu [object!] item [object!]                                       ;   Hence visiting items is implemented here instead on item level only.
        ][ 
            if menu/actor <> item [
                menu/feel/leave menu                                                     
                menu/feel/reveal menu item 
                menu/actor: item
                item/feel/visit item
            ]
        ]
        
        leave: func ["Leaves menu actor, if any." menu [object!] /local item] [ ;-- Leaving an item should never be called explicitly, since it's done
            if item: menu/actor [                                               ;   implicitly by visiting another
                menu/actor: none
                item/feel/leave item                                            ;-- Only if there was an actor, there is a item to leave (and to redraw).
            ]
        ]
        
        root-of: func ["Returns menu's root menu." menu] [
            forever [
                if none? menu/parent [break/return menu]
                menu: menu/parent/menu
            ]
            
        ]
        
        first-of: func ["Returns first item." menu [object!]] [
            foreach item menu/list/pane [
                if 'menu-item = item/type [break/return item]
            ]
        ]

        prev-page-of: func ["Returns previous item." menu [object!] /local extra other] [
            
            extra: any [menu/actor menu/feel/last-of menu]                           
            
            foreach item next find reverse copy menu/list/pane extra [          ;-- This results in other being the last visble item or
                if 'menu-item = item/type [                                     ;   none, if extra itself is the last visible one.
                    if     menu/feel/visible? menu item [other: item]
                    if not menu/feel/visible? menu item [break/return other]
                ]
            ]
            
            either other [
                other                                                           ;-- Returns first item on page
            ][
                menu/actor: extra
                while [
                    all [
                        menu/actor <> menu/feel/first-of menu
                        menu/feel/visible? menu extra
                    ]
                ][
                    menu/feel/visit  menu menu/feel/prev-of menu
                ]
                menu/actor
            ]                                             
        ]
        
        next-page-of: func ["Returns next item." menu [object!] /local extra other] [
            
            extra: any [menu/actor menu/feel/first-of menu] 
            
            foreach item next find menu/list/pane extra [                       ;-- This results in other being the last visble item or
                if 'menu-item = item/type [                                     ;   none, if extra itself is the last visible one.
                    if     menu/feel/visible? menu item [other: item]
                    if not menu/feel/visible? menu item [break/return other]
                ]
            ]
            
            either other [
                other                                                           ;-- Returns last item on page
            ][
                menu/actor: extra
                while [
                    all [
                        menu/actor <> menu/feel/last-of menu
                        menu/feel/visible? menu extra
                    ]
                ][
                    menu/feel/visit  menu menu/feel/next-of menu
                ]
                menu/actor
            ] 
        ]
        
        prev-of: func ["Returns previous item." menu [object!] /wrap "Wrap at menu's top."] [
            either none? menu/actor [
                menu/feel/last-of menu
            ][
                any [
                    foreach item next find reverse copy menu/list/pane menu/actor [
                        if 'menu-item = item/type [break/return item]
                    ]
                    either wrap [menu/feel/last-of menu] [menu/feel/first-of menu]
                ]
            ]
        ]
        
        next-of: func ["Returns next item." menu [object!] /wrap "Wrap at menu's top."] [
            either none? menu/actor [
                menu/feel/first-of menu
            ][
                any [
                    foreach item next find menu/list/pane menu/actor [
                        if 'menu-item = item/type [break/return item]
                    ]
                    either wrap [menu/feel/first-of menu] [menu/feel/last-of menu]
                ]
            ]
        ]

        next-char-of: func [
            "Returns next item starting with char (or NONE)." 
            menu [object!] char [char!] /local items
        ][
            items: either menu/actor [
                items: next find menu/list/pane menu/actor
                append copy items copy/part menu/list/pane items
            ][
                menu/list/pane
            ]
            foreach item items [
                if all [
                    'menu-item = item/type
                    item/body/text
                    equal? uppercase char uppercase item/body/text/1
                ][ 
                    break/return item
                ]
            ]
        ]
        
        last-of: func ["Returns menu's last item." menu [object!]] [
            foreach item reverse copy menu/list/pane [
                if 'menu-item = item/type [break/return item]
            ]
        ]

        visible?: func ["Returns TRUE if is fully visible." menu [object!] item [object!]] [
            not any [
                menu/list/offset/y + item/offset/y < 0                                  ;-- Item is (partially) above the clipping region 
                menu/list/offset/y + item/offset/y + item/size/y > menu/clip/size/y     ;-- Item is (partially) below the clipping region
            ]
        ]

        show-less?: func [menu [object!]] [menu/list/offset/y < 0]
        show-more?: func [menu [object!]] [menu/list/offset/y + menu/list/size/y > menu/panel/size/y]
        hide-less?: func [menu [object!]] [menu/list/offset/y >= - menu/less/size/y]
        hide-more?: func [menu [object!]] [menu/list/offset/y + menu/list/size/y <= (menu/clip/size/y + menu/more/size/y)]
        show-less: func [menu [object!]] [
            if not menu/less/show? [
                menu/clip/offset/y: menu/clip/offset/y + menu/less/size/y
                menu/clip/size/y:   menu/clip/size/y   - menu/less/size/y
                menu/list/offset/y: menu/list/offset/y - menu/less/size/y
                show menu/less
            ]
        ]
        show-more: func [menu [object!]] [
            if not menu/more/show? [
                menu/clip/size/y: menu/clip/size/y - menu/more/size/y
                show menu/more
            ]
        ]
        hide-less: func [menu [object!]] [
            if menu/less/show? [
                menu/clip/offset/y: menu/clip/offset/y - menu/less/size/y       ;-- Move clip to top and
                menu/clip/size/y:   menu/clip/size/y   + menu/less/size/y       ;   grow it.
                menu/less/rate: none
                hide menu/less
            ]
        ]
        hide-more: func [menu [object!]] [
            if menu/more/show? [
                menu/clip/size/y: menu/clip/size/y + menu/more/size/y           ;-- Grow clip.
                menu/list/offset/y: menu/list/offset/y + menu/more/size/y
                menu/more/rate: none
                hide menu/more
            ]
        ]
        
        scroll: func [menu [object!] knob [object!]] [
            case [
                menu/less = knob [
                    menu/list/offset/y: menu/list/offset/y + menu/steps
                    menu/feel/show-more menu
                    if menu/feel/hide-less? menu [menu/feel/hide-less menu]
                ]
                menu/more = knob [
                    menu/list/offset/y: menu/list/offset/y - menu/steps
                    menu/feel/show-less menu
                    if menu/feel/hide-more? menu [menu/feel/hide-more menu]
                ]
            ]                        
            show menu/panel
        ]
    
        reveal: func [
            "Reveals the item (by scrolling the smallest amount necessary)." 
            menu [object!] item [object!]
        /no-show
            "Don't show the changes."
        /local
            delta clip list
        ][
            delta: 0x0 clip: menu/clip list: menu/list
            
            if any [
                if 0 > delta/y:   list/offset/y
                                + item/offset/y [                               ;-- Item is (maybe only partially) above the clipping region 
                    menu/feel/show-more menu
                    list/offset/y: list/offset/y - delta/y
                    if menu/feel/hide-less? menu [menu/feel/hide-less menu]
                    true
                ]
                if 0 < delta/y:   list/offset/y 
                                + item/offset/y 
                                + item/size/y 
                                - clip/size/y [                                 ;-- Item is (maybe onle partially) below the clipping region   
                    menu/feel/show-less menu
                    list/offset/y: list/offset/y - delta/y
                    if menu/feel/hide-more? menu [menu/feel/hide-more menu]
                    true
                ]
            ][
                if not no-show [show menu/panel]
            ]
        ]              
        
        map-key: func [
            "Returns mapped EVENT/KEY."
            menu [object!] event [event!] /local key
        ][
            key: event/key
            if event/control [
                key: any [select [up page-up home home down page-down end end] key key]   ;-- Control key increases key effect.
            ]
            key
        ]
            
        engage: func [menu action event /local actor item key] [
            if event/type = 'key [
                actor: menu/actor  
                key:   menu/feel/map-key menu event
                
                case [
                    'right = key [
                        if all [actor actor/sub] [
                            actor/feel/enter/new actor 
                            item: actor/sub/feel/first-of actor/sub
                            item/menu/feel/visit item/menu item
                            wait []
                        ]
                    ]
                    #" "       = key  or 
                   (#"^M"      = key) [
                        if actor [
                            either actor/sub [
                                actor/feel/enter actor
                            ][
                                actor/feel/engage actor 'up event
                            ]
                        ]
                    ]
                    escape     = key  or
                   ('left      = key) or 
                   ('backspace = key) [menu/feel/close menu]
                    'home      = key  [menu/feel/visit menu menu/feel/first-of     menu]
                    'page-up   = key  [menu/feel/visit menu menu/feel/prev-page-of menu]
                    'up        = key  [menu/feel/visit menu menu/feel/prev-of/wrap menu]
                    'down      = key  [menu/feel/visit menu menu/feel/next-of/wrap menu]
                    'page-down = key  [menu/feel/visit menu menu/feel/next-page-of menu]
                    'end       = key  [menu/feel/visit menu menu/feel/last-of      menu]
                    /default [
                        use [item] [if item: menu/feel/next-char-of menu key [menu/feel/visit menu item]] 
                    ]
                ]
            ]
        ]
    ]
    
    ;=========================================================== menubar-feel ==
    ;
    ;   The MENUBAR-FEEL remaps cursor keys a bit to adjust them to the needs
    ;   of horizontally layouted menubar-items.
    ;
    menubar-feel: make menu-feel [
        super: menu-feel
        detect: func [menubar event][
            menubar/feel/super/pop-detect menubar event
        ]
        close: func [menubar] [
            if menubar/actor [
                if menubar/actor/sub [
                    menubar/actor/sub/feel/close menubar/actor/sub
                ]
                menubar/feel/leave menubar
                menubar/state: 'click-to-enter
                unfocus
            ]
        ]
        reveal: none
        next-page-of: :last-of
        prev-page-of: :first-of
        map-key: func [                                             ;** Overwriting feel/super/map-key, menubars behave different than menus. Hack?
            "Returns mapped EVENT/KEY."
            menu [object!] event [event!] /local key
        ][
            key: any [
                select [left up up right down right] event/key
                event/key
            ]
            if event/control [
                key: any [select [up page-up home home down page-down end end] key key]   ;-- Control key increases key effect.
            ]
            key
        ]
        pop-detect: none
        over: none
        reveal: none
    ]


    ;============================================================= build-menu ==
    ;
    build-menu: func [
        "Builds a menu and it's sub-faces."
        item [object! none!] "the menu's parent item or NONE"
        desc [block!]        "the menu description" 
    /local
        menu options parent
    ][
        menu: make system/view/vid/vid-face [


            ;==================================================== menu-access ==
            ;
            ;   Not delegating SET or GET to a method object is a waste of 
            ;   memory, but I'd really prefer the ease of directly envoking the
            ;   functions over the cluttered delagation syntax here.
            ;
            ;   Compare:
            ;
            ;   • app-menu/access/set app-menu 'file/recent/1 [text: "menus.r"]
            ;   • app-menu/set 'file/recent/1 [text: "menus.r"]
            ;
            ;   Funny though, that in any case there isn't a difference 
            ;   between SETting or GETting, since I'm only DOing code here.
            ;
            
            set: get: func [                                                    
                "Set or get item values (executes code in item's context)."  
                path [path! word!]  "Path to an item anywhere down in the tree"  
                code [block!] "Code executed in item's context"
            /local
                item
            ][  
                item: select items first path: to path! :path
                either empty? next path [
                    do bind :code in item 'self
                ][
                    item/sub/set next path :code
                ]
            ]
            
            ;
            ;-------------------------------------------------------------------
            
    
            
            type: 'menu
            
            feel: menu-feel 
            
            flags: [field]
            
            color: none
            font: none
            edge: make face/edge [
                size: 1x1
                color: none
                colors: reduce [178.180.191 none]
                effect: none
            ]
            

            para: make para [origin: 2x3 wrap?: no]
           
            highlight: 187.183.199                                              ;-- Win XP Silver, hardcoded default
            
            anchor: none                                                        ;-- ANCHOR is used to anchor a menu to an on-screen UI element.
                                                                                ;   
            parent: item
            
            data: desc
            steps: 4                                                            ;-- Dialect this!
            actor: none                                                  
            groups: none 
            items: make block! []                                               ;-- ITEMS holds the menu's items, the block consits
                                                                                ;   of ID / ITEM pairs for easy selection of items
            
            
            shadow: panel: less: clip: list: more: none                           ;-- Accessors for the various sub-faces of a menu.
                                                                                ;   The actual items are to be found in LIST's pane,
                                                                                ;   but clients should use ITEMS.  
            
            pane: reduce [
                shadow: make face  [
                    color: edge: none
                    image: shadow-image
                    effect: [extend alphamul 32]
                ]
                panel: make face  [
                    color: white                                                ;######################
                    edge: none
                    effect: copy [extend 13x33]; merge]
                    pane: reduce [
                        less: make face [                                         
                            color: edge: none                                   
                            feel: knob-feel
                        ]
                        clip: make face [
                            color: edge: none
                            pane: reduce [
                                list: make face [
                                    color: edge: none
                                    pane: make block! []
                                ]
                            ]
                        ]
                        more: make face [                                       ;-- 
                            color: edge: none
                            feel: knob-feel
                        ]
                    ]
                ]
            ]
        ]
        either menu/parent [
            parent: menu/parent/menu
            menu/options: make object! [
                menu: make object! [
                    edge:  parent/options/menu/edge
                    rate:  parent/options/menu/edge
                    color: parent/options/menu/color
                ]
                item: make object! [
                    action:     parent/options/item/action
                    color:      parent/options/item/color
                    highlight:  parent/options/item/highlight
                    body:       make object! [
                        font: parent/options/item/body/font
                        para: parent/options/item/body/para
                    ]
                    key:        make object! [font: parent/options/item/key/font]
                    edge:       parent/options/item/edge
                ]
            ]
        ][
            menu/options: make object! [
                menu: make object! [
                    edge:  system/standard/face/edge
                    rate:  64                                                   ;-- Dialect this
                    color: none
                ]
                item: make object! [
                    action: none
                    color:  none
                    image:     none
                    highlight: none
                    body: make object! [font: para: none]
                    key: make object! [font: none]
                    edge: make face/edge [
                        size:   1x1
                        color:  effect: none
                        colors: reduce [none 178.180.191]                       ;-- Win XP Silver, hardcoded default.
                    ]
                ]
            ]
            menu/options/item/body/font:
            menu/options/item/key/font:  make system/standard/face/font [align: 'left valign: 'middle colors: none]
        ]  
        menu
    ]
    ;---------------------------------------------------------------------------
    

    ;============================================================ layout-menu ==
    ;
    set 'layout-menu func [
        "Returns a menu (face) built from style/content description dialect."
        desc [block!] "Dialect block of styles, attributes, and layouts"
    /parent
        "Bind menu to an item as it's sub-menu."
        item [object!] "A menu-item face"
    /style
        "Base menu on existing style sheet."
        sheet [block!] "A style sheet of menu and item styles."
    /local
        menu   
        value
        
        mark-size icon-size body-size key-size arrow-size                height
        divider item-offset item-size                                   
    ][
        menu: build-menu item desc      

        ;-- Default values are set now to eventually be overwritten by menu spec
        ;
        
        menu/options/item/action: all [menu/parent menu/parent/menu/options/item/action]                      
            
            
        menu/panel/edge: any [                                                  ;-- Setting the menu's edge:
            all [menu/parent menu/parent/menu/panel/edge]                       ;   A menu inherits it's edge from it's parent menu.
            face/edge
        ]
        
        menu/font: any [                                                        ;-- Setting the menu's font:
            all [menu/parent menu/parent/menu/font]                             ;   A menu inherits it's font from it's parent menu.
            face/font                                                           
        ]                                                                       

        if all [                                                                ;-- Setting the menu's panel color:
            menu/parent                                                         ;   A menu inherits it's color from it's parent menu. 
            value: menu/parent/menu/options/menu/color                          ;   Actually, it's not the PANEL-face's color, it's colorized instead.
        ][
            ;insert tail menu/panel/effect reduce ['colorize value]
            menu/panel/color: value
        ]
        
        if menu/parent [                                                        ;-- Setting a menu's watermark:
            menu/panel/image: menu/parent/menu/panel/image                      ;   A menu inherits it's watermark from it's parent menu.
        ] 
 
        if style [insert desc sheet] 
        parse desc [
            any [
                'menu 'style some [
                    'color set value [word! | tuple!] (
                        insert tail menu/panel/effect reduce [
                            'colorize value: cast value [
                                word!  [get value]
                                tuple! [value]
                            ]
                        ]
                        menu/options/menu/color: value
                    )
                |   'edge set value ['none | none! | block!] ( 
                        if value = 'none [value: none]
                        menu/panel/edge: 
                        menu/options/menu/edge: make all [value menu/options/menu/edge] value
                    )
                |   ['backdrop | 'image] set value [word! | file! | image!] (
                        menu/panel/image: cast value [
                            word!  [get value]
                            file!  [load value]
                            image! [value]           
                        ]
                    )
                |   'effect set value [lit-word! | block!] (
                        menu/panel/effect: compose [(value)]
                    )
                ]
            |   'item 'style some [
                    'highlight set value set value [word! | path! | tuple!] (
                        menu/options/item/highlight: cast value [
                            word!  [get value]
                            path!  [do value]
                            tuple! [value]
                        ]
                    )
                |    
                    'font set value block! (
                        menu/options/item/body/font:
                        menu/options/item/key/font: make menu/options/item/body/font value
                    )
                |   'edge set value ['none | none! | block!] (
                        if value = 'none [value: none]
                        menu/options/item/edge: make all [value menu/options/item/edge] value                      
                    )
                |   
                    'action set value block! (menu/options/item/action: value)
                ]
            ]
            desc:
            to end
        ]
        
        layout-items menu desc
        menu/list/pane: head menu/list/pane
        
        menu
    ]        
    ;---------------------------------------------------------------------------
    
        
    ;=========================================================== layout-items ==
    ;
    set 'layout-items func [
        "Layouts items."
        menu [object!]
        desc [block!]
    /local
        value item
    ][
        parse desc [
            some [
                [   ['divider | 'bar | '---] (                                        
                        item: build-divider menu
                        menu/list/pane: insert menu/list/pane item
                        insert tail menu/items reduce [none item]
                    )
                |   
                    set value opt set-word! (
                        value: all [:value to word! value]
                    )
                    'item (
                        item: build-item menu value desc
                        menu/list/pane: insert menu/list/pane item
                        insert tail menu/items reduce [item/var item]
                    )
                    
                    any [
                        set value string! (
                            item/body/text: value
                        )
                    |   ['action set value block! | set value block!] (
                            item/options/action: value
                        )
                    |   'icon set value [image! | word! | file! | path! | block!] (
                            item/icon/image: cast value [
                                word!  [get value]
                                file!  [load value]
                                image! [value]
                                path!  [do value]
                            ]
                            if block? value [
                                item/icon/image: first value: reduce value
                                item/options/icons: value
                            ]
                        ) 
                    |   'highlight set value set value [word! | path! | tuple!] (
                            item/options/highlight: cast value [
                                word!  [get value]
                                path!  [do value]
                                tuple! [value]
                            ]
                        )
                    |   'font set value [block!] (
                            ;make error! "item font set!"
                        ) 
                    |   'body [
                            'font set value [block!] (
                                item/body/font: item/options/body/font: make item/options/body/font value
                            )
                        |   'para set value [block!] (
                                item/body/para: item/options/body/para: make item/options/body/para value    
                            )
                        ]
                    |   'key 'font set value [block!] (
                            item/key/font: item/options/key/font: make item/options/key/font value
                        ) 
                    |   'edge set value ['none | none! | block!] (
                            if value = 'none [value: none]
                            item/edge: item/options/edge: make all [value item/options/edge] value                       
                        )
                    |   set value ['radio | 'check] (
                            item/mark/type: value
                            item/state: off
                        )
                        any [
                            'of [opt 'group] set value [lit-word!] (
                                item/group: value
                            )
                        |   set value [
                                'on  | 'true  | 'yes | true |
                                'off | 'false | 'no  | false
                            ](
                                item/state: do value
                            )
                        ]
                    |   'menu copy value block! (
                            item/data: first value                              ;-- Remember a sub-menu's description
                            
                            item/sub: layout-menu/parent item/data item         ;-- And set up the sub-menu's faces
                            
                        )
                    |   'image set value [image! | file! | word! | path!] (
                            item/options/image: cast value [
                                file!  [load value]
                                word!  [get value]
                                path!  [do value]
                                image! [value]
                            ]
                        ) 
                    |   set value ['disable | 'enable] (
                            item/disabled?: value = 'disable
                        )  
                    |   set value tag! (
                            item/key/text: to string! value
                        )           
                    ]
                ]
            ]
        ]
        
        menu
    ] 
    ;---------------------------------------------------------------------------   


    ;=========================================================== adjust-items ==
    ;
    ;   ADJUST-ITEMS' job is to establish consistent item sizes within one
    ;   menu. It makes all items the same width (which may be a fixed width
    ;   or the width of the widest item). It further aligns the details of
    ;   one item to be in column with the corresponding details of other items.
    ;
    adjust-items: func [
        "Adjust the menu-items widths."
        items [block!]
    /width
        w
    /local
        item      mark      icon      body      key      arrow     
        item-size mark-size icon-size body-size key-size arrow-size
    ][
        ;-- Measure the items to find the maximums
        ;
        ;   Two pass: First loop adjusts the height of items while collecting
        ;   information about their widths.
        ;   The second loop applies the maximas to align the detail faces.
        ;
        item-size: mark-size: icon-size: body-size: key-size: arrow-size: 0x0
        foreach item items [
            set [mark icon body key arrow] reduce bind [mark icon body key arrow] in item 'self  
            item-size/y: 0
            edge-size: edge-size? item
            switch item/type [
                menu-item [
                     mark-size: max  mark-size  mark/size: 16x0
                     icon-size: max  icon-size  icon/size: image-size? icon
                     body-size: max  body-size  body/size: max 0x4 + text-size? body any [all [item/options/image item/options/image/size] 0x0]  
                      key-size: max   key-size   key/size: max text-size? key  image-size? key 
                    arrow-size: max arrow-size arrow/size: 16x0
                    
                    item/size/y: mark/size/y: ;icon/size/y: 
                    body/size/y: key/size/y: arrow/size/y: first maximum-of reduce [
                        mark/size/y icon/size/y body/size/y key/size/y arrow/size/y
                    ] 
                    
                     item-size: first maximum-of reduce [mark-size icon-size body-size key-size arrow-size] 
                    
                     icon/offset/y: max item/size/y - icon/size/y / 2 0
                     
                     body/size/y:
                      key/size/y: item/size/y  
                ]
                menu-divider [ ]                                                ;-- For now, dividers are somewhat static, so currently this is a no-op.   
            ]   
            item/size/y: item/size/y + second edge-size? item   
        ]
        
        ;-- Apply those maximums to the smaller ones
        ;
        item-offset: 0x0 
        item-size/x: mark-size/x + icon-size/x + body-size/x + key-size/x + arrow-size/x + 2
        foreach item items [
            set [mark icon body key arrow] reduce bind [mark icon body key arrow] in item 'self  
            item/offset/y: item-offset/y
            item/size/x: item-size/x + first edge-size? item
            switch item/type [
                menu-item [
                    mark/size/x: mark-size/x
                     icon/size/x: icon-size/x
                     body/size/x: body-size/x
                      key/size/x:  key-size/x   
                      
                    mark/offset/x: 0
                     icon/offset/x: mark/offset/x + mark/size/x
                     body/offset/x:  icon/offset/x +  icon/size/x 
                      key/offset/x:  body/offset/x +  body/size/x 
                    arrow/offset/x:   key/offset/x +   key/size/x 
                    
                    if item/options/image [
                        if none? item/body/effect [item/body/effect: copy []]
                        insert item/body/effect compose/deep [
                            draw [image (as-pair 0 item/body/size/y - item/options/image/size/y / 2) (item/options/image)]
                        ]
                    ]
                    
                    if logic? item/state [                                      ;-- Wouldn't this better be ITEM/MARK/STATE ?
                        mark/size/y: item/size/y
                        either item/mark/type = 'check [draw-mark item] [draw-radio item]
                    ]
                
                    if item/sub [                                               ;-- Obscured for LAYOUT-MENU/SELECTED purposes :(
                        arrow/size/y: item/size/y
                        draw-arrow item
                    ]
                    
                    item/size/y: item/size/y + second edge-size? item
                    
                ]
            
                menu-divider [
                    item/size/x: item-size/x 
                    item/size/y: 5                                             ;-- This will change with dividers become arrow configurable
                ]
            ]   
            item-offset/y: item/offset/y + item/size/y
        ]
        item-size  
    ]
    ;---------------------------------------------------------------------------
         
    
    ;============================================================ adjust-menu ==
    ;
    
    adjust-menu: func ["Adjusts the menus size." menu [object!]] [
        adjust-items menu/list/pane
    ]
    
    
    ;============================================================== show-menu ==
    ;
    ;   SHOW-MENU 
    set 'show-menu func [
        window [object!]
        menu [object!]
    /offset
        at [pair!]
    /size
        "Restrict menu's size"
        max-size [pair!]
    /new
        "Opens a new window and returns immediately."                           ;-- Works like in VIEW.
    /local   
        value
        item divider item-offset item-size     
    ][
        max-size: any [max-size window/size]        
        menu/actor: none                                                        ;-- Reset ACTOR (may still be set from last time the menu has been shown)
                                                                                ;   **************************** Really? ********************************
        item-size: adjust-items menu/list/pane
        
        
         
        ;-- Preparation of the less- and more-knobs, we'll may need them.
        ;        
        menu/less/size: menu/more/size: 1x0 * item-size/x + 0x12
        draw-knob menu 'less   
        draw-knob menu 'more
        
        
        ;-- Let's see how big the menu may get.
        ;
        menu/list/offset: 0x0
        menu/list/size:   second span? menu/list/pane
        
        menu/clip/size/x: menu/list/size/x
        
        menu/panel/offset:  0x0
        menu/shadow/offset: 0x0
        
        menu/less/offset:
        menu/clip/offset: 
        menu/more/offset: 2x2
        
        either (second menu/list/size + (edge-size? menu/panel) + 4x4 + 4x4) > max-size/y [
            menu/less/show?: no menu/more/show?: yes
            menu/clip/size/y: max-size/y                                                          ;-- of course this is only correct for offset/y = 0 and max-size/y = window/size/y
        ][
            menu/less/show?: menu/more/show?: no
            menu/clip/size/y: menu/list/size/y
        ]   

        
        menu/more/offset/y: menu/clip/offset/y + menu/clip/size/y
        
        
        menu/panel/size: 4x4 + menu/clip/size + edge-size? menu/panel
        if menu/less/show?   [menu/panel/size/y: menu/panel/size/y + menu/less/size/y] 
        if menu/more/show? [menu/panel/size/y: menu/panel/size/y + menu/more/size/y] 
        
        menu/panel/effect
        
        menu/shadow/size: menu/panel/size + 4x4                                 ;-- The additional 4x4 pixels increase in size is a hardcoded
                                                                                ;   value depending of the shadow.png-image used to draw the shadow.
                                                                                ;   It may vary if other images will be allowed for the menu shadow,
                                                                                ;   but how and where account for that?
        
        
        ;-- Calculating offset --
        ;
        ;   Position the sub-menu at the right edge on the same line with
        ;   the actor.
        ;
        at: any [
            at
            if menu/parent [
                at: win-offset? menu/parent
                at/x: at/x + menu/parent/size/x
                if menu/parent/menu/edge [
                    at/x: at/x + menu/parent/menu/edge/size/x
                ]
                if menu/panel/edge [
                    at/y: at/y - menu/panel/edge/size/y
                ]
                at/y: at/y + (menu/parent/size/y - menu/list/pane/1/size/y / 2)
                at/y: at/y - 2
                at
            ]
            0x0
        ]
        
        menu/size:   menu/shadow/size 
        
        ;-- Right border check --
        ;
        if at/x + menu/size/x > window/size/x [
            either menu/parent [
                at/x: max 0 at/x - menu/panel/size/x - menu/parent/size/x
            ][
                at/x: max 0 window/size/x - menu/size/x
            ]
        ]
        
        ;-- Bottom border check --
        ;
        if at/y + menu/size/y > window/size/y [
            at/y: max 0 window/size/y - menu/size/y
        ]
        
        ;-- Size check, again --
        ;
        if at/y + menu/size/y > window/size/y [
            menu/size/y: menu/shadow/size/y: window/size/y
            menu/panel/size/y: menu/size/y - 4
            menu/clip/size/y: menu/panel/size/y - 4 - menu/more/size/y
            menu/more/show?: yes
            menu/more/offset/y: menu/clip/size/y
        ] 
        
        menu/offset: at
        
        ;== Finally, we've done it, so let's start the show 
        ;
        show-popup/window/away menu window 
        focus/no-show menu
        unless new [wait []]
    ]
    ;---------------------------------------------------------------------------


    ;======================================================== remove-items-of == 
    ;
    set 'add-menu func [
        menu [object!] "The menu to remove from"
        item [object!] "The item to remove"
    ][
        remove any [find menu/list/pane item []]
    ]
    ;---------------------------------------------------------------------------
  
    {
    add-items-to    app-menu 'file/recent [recent-5: item "www.rebol.org"]
    remove-items-of app-menu 'file/recent/recent-5                              ;-- Identifies item by VAR word.
    remove-items-of app-menu 'file/recent/5                                     ;-- Identifies item by position in menu
    }
    
    ;=========================================================== add-items-to ==
    ;
    set 'insert-menu func [
        "Inserts a menu item (even some) into an existing menu."
        menu [object!] "The menu to add to"
        desc [block!]  "Description of one or more menu items"
    /head
        "Add item at the head of the menu."
    /before
        "Inserts item before the specified successor." 
        succ [object!]
    /after
        "Inserts item after the specified predecessor" 
        pred [object!]
    /tail
        "Inserts item at the tail of the menu (default)."
    /local
        pane new
    ][
        menu/list/pane: any [
            if head   [menu/list/pane]
            if before [find menu/list/pane succ]
            if after  [next find menu/list/pane pred] 
            system/words/tail menu/list/pane
        ]
        type? menu/list/pane
        new: build-menu none desc
        insert menu/list/pane new/list/pane
        
        layout-items menu desc 
        menu/list/pane: head menu/list/pane
    ]
    ;---------------------------------------------------------------------------
            

    ;=============================================================== menu-bar ==
    ;
    
    stylize/master [
        menu-bar: face with [
            flags: [field]
            type: 'menu-bar
            style: none
            parent: none
            state: 'click-to-enter
            list:  self ;-- To make all the menu/list/pane paths from menubar/feel/super/... work.
            set: get: func [                                                    ;-- Updating e.g. text of menubar-items is problematic, but it would be useful for e.g. disabling these                                          
                "Set or get item values (executes code in item's context)."  
                path [path! word!]  "Path to an item anywhere down in the tree"  
                code [block!] "Code executed in item's context"
            /local
                item
            ][  
                item: select items first path: to path! :path
                either empty? next path [
                    do bind :code in item 'self
                    show item
                ][
                    item/sub/set next path :code
                ]
            ]
            colors: reduce [none 178.180.191]
            font: make font [name: "Tahoma" size: 11 color: black offset: space: 0x0 shadow: none]
            color: none edge: none actor: none items: none
            words: [
                menu       [new/data: first next args next args]
                menu-style [new/style: first next args next args]
            ]
            init: [
                pane:  copy []
                list:  self                                                     ;** To make all the menu/list/pane paths from menubar/feel/super/... work. Hack?
                items: copy []
                feel: ctx-menus/menubar-feel 
                if all [style data] [insert data style]
                use [value menu-bar specs item item-offset actor specs] [
                    menu-bar: self
                    parse data [
                    
                        copy specs to set-word! (
                            if none? specs [specs: copy [menu style item style]]
                        )
                        
                        some [
                            set value set-word! 'item (
                                insert tail pane item: make face [
                                    type:   'menu-item
                                    menu:   menu-bar
                                    var:    to word! value
                                    font:   menu-bar/font
                                    offset: size: 0x0
                                    edge: none
                                    sub: none
                                    para: make face/para [origin: 5x4 margin: none wrap?: no align: 'left valign: 'middle]
                                    feel: ctx-menus/baritem-feel
                                ]
                                insert tail menu-bar/items reduce [item/var item]
                            )
                        
                            any [
                                set value string! (
                                    item/text: value
                                )
                            |   'menu set value block! (
                                    item/data: insert value specs 
                                    item/sub: layout-menu value
                                    item/sub/parent: item                       ;-- Is this hack potentially dangerous? 
                                )
                            ]
                            
                        ]
                    ]
                    
                    item-offset: 0x0
                    foreach item pane [
                        item/size: add text-size? item edge-size? item
                        if all [item/para item/para/origin] [
                            item/size: item/size + (2 * item/para/origin)
                        ]
                        item-offset: item/size + item/offset: 1x0 * item-offset
                    ]
                    
                ]
                size: add second span? pane edge-size? self
            ]
            multi: make multi [
                file: func [face blk] [
                    if pick blk 1 [
                        face/data: load first blk
                    ]
                ]
            ]
        ]
    ]
      
    ;============================================================== drop-menu ==
    ;

    stylize/master [
        drop-menu: field with [
            style: none
            size: 100x21 
            font: make face/font [offset: 2x6 colors: reduce [black black] name: "Tahoma" size: 11 align: 'left]
            edge: make face/edge [size: 1x1 effect: none color: 178.180.191]
            para: make face/para [wrap?: no margin: 22x5]
            feel: make feel [
                redraw: func [face act pos] bind [
                    if all [in face 'colors block? face/colors] [
                        face/color: pick face/colors face <> focal-face
                    ] 
                    if all [in face/font 'colors block? face/colors] [
                        face/font/color: pick face/font/colors face <> focal-face
                    ]
                ] in system/view 'self
                engage: func [face act event] bind [
                    switch act [
                        down [
                            either equal? face focal-face [unlight-text] [focus/no-show face] 
                            caret: offset-to-caret face event/offset 
                            show face
                        ] 
                        over [
                            if not-equal? caret offset-to-caret face event/offset [
                                if not highlight-start [highlight-start: caret] 
                                highlight-end: caret: offset-to-caret face event/offset 
                                show face
                            ]
                        ] 
                        key [
                            either event/key = 'down [
                                ;do in face/pane/feel 'engage face/pane 'up event
                                face/pane/action
                            ][    
                                ctx-text/edit-text face event get in face 'action
                            ]
                        ]
                    ]
                ] in system/view 'self
            ]
            menu: none
            words: [
                menu [new/data: first next args next args]
                menu-style [new/style: first next args next args]
            ]
            init: [
                if all [style data] [insert data style]
                use [anchor parent] [
                    
                    parent: anchor: self
                    if not string? text [text: either text [form text] [copy ""]] 
                    ;if not flag-face? self hide [data: text]
                    colors: reduce [white yellow + 64.64.64] 
                    pane: make-face/spec 'btn [
                        effect: [extend 14 draw [pen 0.0.0 fill-pen 0.0.0 polygon 5x7 11x7 8x10]]
                        size: 17x17
                        offset: 1x0 * parent/size - 20x-1
                        set in parent 'menu layout-menu parent/data
                        action: [
                            unfocus
                            show-menu/offset find-window parent parent/menu (win-offset? parent) + (0x1 * parent/size) - 1x2
                        ]
                    ]
                ]
            ]
        ]
        
    ]
    ;---------------------------------------------------------------------------


]
;-------------------------------------------------------------------------------