area: make rebface [ tip: { USAGE: area area "Text" -1 area "Text" 50x-1 DESCRIPTION: Editable text area with wrapping and scroller. OPTIONS: 'info specifies read-only } size: 50x25 text: "" color: colors/page edge: theme-edge font: default-font-top para: make default-para-wrap [margin: as-pair sizes/slider + 2 2] feel: make edit/feel [ redraw: func [face act pos /local height total visible] [ if act = 'show [ ; check for size change and resize scroller if face/size <> face/old-size [ face/pane/offset/x: max 0 face/size/x - face/pane/size/x face/pane/size/y: face/size/y ] if any [ face/text-y <> height: second size-text face ; height of text changed ? face/size <> face/old-size ; size changed ? ][ face/text-y: height total: face/text-y visible: face/size/y - (edge/size/y * 2) - para/origin/y - para/indent/y face/pane/ratio: either total > 0 [min 1 (visible / total)][1] ; update scroller step face/pane/step: either visible < total [min 1 (sizes/font-height / (total - visible))][0] ] ; Only update slider/data if scroll was caused by a key (not by slider itself). Avoids recursion. if all [face/pane/ratio < 1 face/key-scroll?] [ do bind [ ; Update slider dragger position to reflect para/scroll/y ; para/scroll is relative to edge/size + para/origin + (para/indent * 0x1) total: text-y visible: size/y - (edge/size/y * 2) - para/origin/y - para/indent/y pane/data: - para/scroll/y / (total - visible) ] face face/key-scroll?: false ] ] ] ] esc: none caret: none undo: copy [] text-y: none key-scroll?: false ; this is set to true by edit/edit-text to bypass slider action action: make default-action [ on-scroll: make function! [face scroll /page /local total visible] [ total: second size-text face visible: face/size/y - (face/edge/size/y * 2) - face/para/origin/y - face/para/indent/y face/para/scroll/y: either page [ min max face/para/scroll/y - (visible * sign? scroll/y) (visible - total) 0 ][ min max face/para/scroll/y - (scroll/y * sizes/font-height) (visible - total) 0 ] ; Update slider dragger position to reflect para/scroll/y ; para/scroll is relative to edge/size + para/origin + (para/indent * 0x1) all [face/pane/data: - face/para/scroll/y / (total - visible)] show face ] ] rebind: make function! [] [ color: colors/page para/margin/x: sizes/slider + 2 ] init: make function! [/local p] [ if find options 'info [ feel: make feel [engage: none] all [color = colors/page color: colors/outline-light] ] para: make para [] ; avoid shared para object for scrollable input widget p: self text-y: second size-text self all [negative? size/x size/x: 10000 size/x: 4 + first size-text self] all [negative? size/y size/y: 10000 size/y: 8 + text-y] pane: make slider [ tip: none offset: as-pair p/size/x - sizes/slider 0 size: as-pair sizes/slider p/size/y span: case [ none? p/span [none] all [find p/span #H find p/span #W] [#XH] find p/span #W [#X] find p/span #H [#H] true [none] ] options: [arrows] action: make default-action [ on-click: make function! [face /local visible] [ ; Only update slider/data if scroll was caused by a key (not by slider itself). Avoids recursion. unless parent-face/key-scroll? [ visible: (parent-face/size/y - (parent-face/edge/size/y * 2) - parent-face/para/origin/y - parent-face/para/indent/y) parent-face/para/scroll/y: negate parent-face/text-y - visible * data if all [ view*/caret parent-face = view*/focal-face ][ ; Keep caret inside the visible part of the area ; view*/caret: offset-to-caret parent-face min max ; (caret-to-offset parent-face view*/caret) ; get the current position of the caret ; (sizes/font-height * 0x1) ; minimum, plus height of one line of text, to keep caret fully visible ; (parent-face/size - (face/size * 1x0) - (sizes/font-height * 0x1)) ; maximum, subtract height of one line of text ] ; -AntonR show parent-face ] parent-face/key-scroll?: false ] ] ratio: p/size/y - 4 / text-y ] pane/init ] ]