├── .gitignore ├── LICENSE.txt ├── README.md ├── images └── hn.png ├── info.rkt ├── main.rkt └── scribblings └── canvas-list.scrbl /.gitignore: -------------------------------------------------------------------------------- 1 | compiled/ 2 | doc/ 3 | *.bak 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jeffrey Massung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Racket Canvas List 2 | 3 | This is a fast-rendering, single-selection, canvas control allowing custom drawing for a list of items. It can handle extremely large lists as it only renders what's visible on the screen. Additionally, it supports: 4 | 5 | * Single selection 6 | * Keyboard and mouse navigation 7 | * Context menus 8 | * Mouse hovering 9 | * Alternate row colors 10 | * Primary key indexing with sorting and filtering 11 | 12 | ## Example Usage 13 | 14 | ```racket 15 | (define frame 16 | (new frame% 17 | [label "List Canvas"] 18 | [width 260] 19 | [height 400])) 20 | 21 | (define canvas 22 | (new canvas-list% 23 | [parent frame] 24 | [items (range 1000)] 25 | [action-callback (λ (canvas item) 26 | (displayln item))])) 27 | 28 | (send frame show #t) 29 | ``` 30 | 31 | ## Documentation 32 | 33 | Check out the [scribble documentation][doc]. 34 | 35 | ## Screenshot 36 | 37 | This is a screenshot of my own, personal, Hacker News reader made with this control. 38 | 39 | ![](images/hn.png) 40 | 41 | [doc]: https://docs.racket-lang.org/canvas-list/index.html 42 | -------------------------------------------------------------------------------- /images/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massung/racket-canvas-list/8d101e6ede48be4c77673dddc32442f55a42f5c2/images/hn.png -------------------------------------------------------------------------------- /info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define collection "canvas-list") 4 | (define version "1.0.1") 5 | (define pkg-authors '("massung@gmail.com")) 6 | (define pkg-desc "Fast-rendering, single-selection, canvas control allowing custom drawing for a list of items.") 7 | (define deps '("base" "draw-lib" "gui-lib" "draw-doc" "gui-doc" "racket-doc" "scribble-lib")) 8 | (define scribblings '(("scribblings/canvas-list.scrbl" ()))) 9 | (define compile-omit-paths '("images")) 10 | -------------------------------------------------------------------------------- /main.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/gui 2 | 3 | 4 | #| 5 | 6 | Racket Canvas List 7 | 8 | Copyright (c) 2021 by Jeffrey Massung 9 | All rights reserved. 10 | 11 | -- 12 | 13 | This is a fast-rendering, single-selection, canvas control allowing 14 | custom drawing for a list of items. 15 | 16 | See https://github.com/massung/racket-canvas-list for documentation 17 | and example usage. 18 | 19 | |# 20 | 21 | 22 | (provide canvas-list%) 23 | 24 | 25 | (require racket/draw) 26 | (require racket/match) 27 | 28 | 29 | ;; create one big canvas to render the stories on 30 | (define canvas-list% 31 | (class canvas% 32 | (super-new [style '(vscroll no-autoclear)] 33 | [paint-callback (λ (canvas dc) (paint dc))]) 34 | 35 | ;; public fields 36 | (init-field [items #()] 37 | [item-height 20] 38 | 39 | ;; do not allow no selection 40 | [force-selection #f] 41 | 42 | ;; item colors 43 | [item-color (make-color #xff #xff #xff)] 44 | [alt-color (make-color #xf8 #xf8 #xf8)] 45 | [selection-color (make-color #x99 #xcc #xff)] 46 | [hover-color (make-color #xbb #xdd #xff)] 47 | 48 | ;; callbacks 49 | [paint-item-callback #f] 50 | [selection-callback #f] 51 | [action-callback #f] 52 | [context-action-callback #f]) 53 | 54 | ;; index mapping 55 | (define primary-key #()) 56 | 57 | ;; the summed height of all display items 58 | (define display-height 0) 59 | 60 | ;; hovered-over and selected item 61 | (define hover-index #f) 62 | (define selected-index #f) 63 | 64 | ;; horizontal and vertical scroll offsets 65 | (define h-offset 0) 66 | (define v-offset 0) 67 | 68 | ;; the last time a click event was received 69 | (define click-time 0) 70 | 71 | ;; reset the primary key index 72 | (define/public (reset-primary-key) 73 | (set! primary-key (build-vector (vector-length items) identity))) 74 | 75 | ;; return the number of items in the list 76 | (define/public (count-items) 77 | (vector-length primary-key)) 78 | 79 | ;; return the item at the given index 80 | (define/public (get-item index) 81 | (let ([n (- (vector-length primary-key) 1)]) 82 | (and index (<= 0 index n) (vector-ref items (vector-ref primary-key index))))) 83 | 84 | ;; set the list of items 85 | (define/public (set-items xs) 86 | (set! items (for/vector ([x xs]) x)) 87 | (reset-primary-key) 88 | 89 | ; update scrolling and redraw 90 | (send this set-scroll-pos 'vertical 0) 91 | (update-scrollbar) 92 | (if (and force-selection selected-index) 93 | (select-first) 94 | (clear-selection)) 95 | (send this refresh)) 96 | 97 | ;; sort the items 98 | (define/public (sort-items less-than? #:key [key #f]) 99 | (let ([sel (and selected-index (vector-ref primary-key selected-index))]) 100 | (vector-sort! primary-key 101 | less-than? 102 | #:cache-keys? #t 103 | #:key (λ (i) 104 | (let ([item (vector-ref items i)]) 105 | (if key (key item) item)))) 106 | (select-index (vector-member sel primary-key)))) 107 | 108 | ;; filter items 109 | (define/public (filter-items pred #:key [key #f]) 110 | (let ([sel (and selected-index (vector-ref primary-key selected-index))]) 111 | (when key 112 | (set! pred (λ (item) (pred (key item))))) 113 | (set! primary-key (vector-filter (λ (i) (pred (vector-ref items i))) primary-key)) 114 | (select-index (vector-member sel primary-key)) 115 | (update-scrollbar))) 116 | 117 | ;; clear the list of items 118 | (define/public (clear) 119 | (set! hover-index #f) 120 | (set! selected-index #f) 121 | (set! v-offset 0) 122 | (set-items #())) 123 | 124 | ;; replace an item at the provided - or selected - index 125 | (define/public (set-item x [index selected-index]) 126 | (when index 127 | (vector-set! items (vector-ref primary-key index) x) 128 | (send this refresh))) 129 | 130 | ;; insert an item before the provided - or selected - index 131 | (define/public (insert-items xs [index #f]) 132 | (set-items (if index 133 | (let-values ([(left right) (vector-split-at items index)]) 134 | (vector-append left (for/vector ([x xs]) x) right)) 135 | (vector-append (for/vector ([x xs]) x) items)))) 136 | 137 | ;; append new items to the list 138 | (define/public (append-items xs [index #f]) 139 | (if index 140 | (insert-items xs (+ index 1)) 141 | (set-items (vector-append items (for/vector ([x xs]) x))))) 142 | 143 | ;; return the currently hovered and selected item indices 144 | (define/public (get-hover-index) hover-index) 145 | (define/public (get-selected-index) selected-index) 146 | 147 | ;; return the currently hovered over item 148 | (define/public (get-hover-item) 149 | (get-item hover-index)) 150 | 151 | ;; return the currently selected item 152 | (define/public (get-selected-item) 153 | (get-item selected-index)) 154 | 155 | ;; execute a callback with the selected item 156 | (define/public (call-with-selected-item f) 157 | (let ([item (get-selected-item)]) 158 | (when item 159 | (f item)))) 160 | 161 | ;; when the scroll position updates, refresh 162 | (define/override (on-scroll event) 163 | (let ([h (send this get-scroll-range 'vertical)] 164 | [pos (send event get-position)]) 165 | (when (eq? (send event get-direction) 'vertical) 166 | (set! v-offset (min (max pos 0) h)))) 167 | (super on-scroll event) 168 | (send this refresh)) 169 | 170 | ;; whenever resized, update the scrollbar range 171 | (define/override (on-size width height) 172 | (super on-size width height) 173 | (update-scrollbar)) 174 | 175 | ;; handle mouse events 176 | (define/override (on-event event) 177 | (case (send event get-event-type) 178 | ('left-down (click event)) 179 | ('right-down (r-click event)) 180 | ('motion (update-hover-index event)) 181 | ('leave (update-hover-index #f)))) 182 | 183 | ;; handle key events 184 | (define/override (on-char event) 185 | (let ([ds (exact-truncate item-height)]) 186 | (case (send event get-key-code) 187 | ('down (select-next)) 188 | ('up (select-previous)) 189 | ('next (select-next #:advance (visible-items))) 190 | ('prior (select-previous #:advance (visible-items))) 191 | ('home (select-first)) 192 | ('end (select-last)) 193 | ('escape (clear-selection)) 194 | ('clear (clear-selection)) 195 | ('wheel-up (scroll-relative (- ds))) 196 | ('wheel-down (scroll-relative (+ ds))) 197 | ((#\space) (scroll-to-selection)) 198 | ((#\return) (open-selected-item))))) 199 | 200 | ;; update the scroll position so the selection is visible 201 | (define/public (scroll-to-selection) 202 | (when selected-index 203 | (let* ([pos (* selected-index item-height)] 204 | [cur-pos (send this get-scroll-pos 'vertical)] 205 | [h (send this get-height)] 206 | [m (- (+ h cur-pos) item-height)] 207 | [new-pos (cond 208 | [(<= cur-pos pos m) cur-pos] 209 | [(< pos cur-pos) pos] 210 | [else (+ (- pos h) item-height)])]) 211 | (send this set-scroll-pos 'vertical new-pos) 212 | (send this on-scroll (new scroll-event% [position new-pos]))))) 213 | 214 | ;; the selected index should be acted on 215 | (define/public (open-selected-item) 216 | (when (and selected-index action-callback) 217 | (action-callback this (get-selected-item) #f))) 218 | 219 | ;; change the selected item 220 | (define/public (select-index [index hover-index]) 221 | (unless (and force-selection (not index)) 222 | (set! selected-index index) 223 | (when selected-index 224 | (when selection-callback 225 | (selection-callback this (get-selected-item) #f)) 226 | (scroll-to-selection)) 227 | (send this refresh))) 228 | 229 | ;; clear the current selection 230 | (define/public (clear-selection) 231 | (unless (and force-selection (positive? (count-items))) 232 | (select-index #f))) 233 | 234 | ;; select the first item 235 | (define/public (select-first) 236 | (when (positive? (count-items)) 237 | (select-index 0))) 238 | 239 | ;; select the last item 240 | (define/public (select-last) 241 | (let ([n (count-items)]) 242 | (when (positive? n) 243 | (select-index (- n 1))))) 244 | 245 | ;; select the next item 246 | (define/public (select-next #:advance [n 1]) 247 | (if (not selected-index) 248 | (select-first) 249 | (let ([m (- (count-items) 1)]) 250 | (select-index (max (min (+ selected-index n) m) 0))))) 251 | 252 | ;; select the previous item 253 | (define/public (select-previous #:advance [n 1]) 254 | (select-next #:advance (- n))) 255 | 256 | ;; return the number of visible items 257 | (define/private (visible-items) 258 | (exact-truncate (/ (send this get-height) item-height))) 259 | 260 | ;; return the first visible index 261 | (define/private (first-visible) 262 | (let-values ([(q r) (quotient/remainder v-offset item-height)]) 263 | (+ q (if (positive? r) 1 0)))) 264 | 265 | ;; return the last visible index 266 | (define/private (last-visible) 267 | (min (+ (first-visible) (visible-items)) (- (count-items) 1))) 268 | 269 | ;; update the vertical scrollbar range 270 | (define/private (update-scrollbar) 271 | (let* ([pos (send this get-scroll-pos 'vertical)] 272 | 273 | ; number of items to display and visible height 274 | [n (count-items)] 275 | [h (send this get-height)] 276 | 277 | ; scroll height of all items less a single screen 278 | [y (- (* n item-height) h)] 279 | [page (max (- h item-height) 1)] 280 | 281 | ; is the scroll bar visible? 282 | [show (> y 0)]) 283 | (send this show-scrollbars #f show) 284 | 285 | (when show 286 | (send this init-manual-scrollbars #f y 1 page 0 0) 287 | 288 | ; keep the same scroll position if possible 289 | (let ([new-pos (min pos y)]) 290 | (send this set-scroll-pos 'vertical new-pos) 291 | (send this on-scroll (new scroll-event% [position new-pos])))))) 292 | 293 | ;; move the scrollbar to an absolute position 294 | (define/private (scroll-to pos) 295 | (let ([h (send this get-scroll-range 'vertical)]) 296 | (set! v-offset (min (max pos 0) h)) 297 | (send this set-scroll-pos 'vertical v-offset) 298 | (send this refresh-now))) 299 | 300 | ;; move the scrollbar relative to its current position 301 | (define scroll-position-delta 0) 302 | (define scroll-flush-delay-ms 8) ;; 120 fps 303 | (define scroll-flush-scheduled? #f) 304 | 305 | ;; sent while scrolling to update the scroll position 306 | (define/private (flush-scroll-position dpos) 307 | (scroll-to (+ (send this get-scroll-pos 'vertical) dpos))) 308 | 309 | ;; Scrolling is a relatively expensive operation and on platforms 310 | ;; that generate a lot of scroll wheel events (like when using a 311 | ;; trackpad on macOS), we need some form of "frame skipping" in 312 | ;; order to correctly handle situtations where the user starts 313 | ;; scrolling in one direction then changes direction mid scroll. 314 | (define/private (scroll-relative dpos) 315 | (define deadline-evt (alarm-evt (+ (current-inexact-milliseconds) scroll-flush-delay-ms))) 316 | (set! scroll-position-delta (+ dpos scroll-position-delta)) 317 | (unless scroll-flush-scheduled? 318 | (set! scroll-flush-scheduled? #t) 319 | (thread (λ () 320 | (sync deadline-evt) 321 | (queue-callback (λ () 322 | (set! scroll-flush-scheduled? #f) 323 | (flush-scroll-position 324 | (begin0 scroll-position-delta 325 | (set! scroll-position-delta 0))))))))) 326 | 327 | ;; the left mouse button was clicked 328 | (define/private (click event) 329 | (let ([now (current-inexact-milliseconds)]) 330 | (if (and (equal? hover-index selected-index) 331 | (< (- now click-time) 200)) 332 | (when action-callback 333 | (action-callback this (get-selected-item) event)) 334 | (select-index)) 335 | (set! click-time now))) 336 | 337 | ;; the right mouse button was clicked 338 | (define/private (r-click event) 339 | (unless (eq? hover-index selected-index) 340 | (select-index) 341 | (send this refresh-now)) 342 | (when context-action-callback 343 | (context-action-callback this (get-selected-item) event))) 344 | 345 | ;; update which story is being hovered over 346 | (define/private (update-hover-index event) 347 | (if event 348 | (let* ([y (send event get-y)] 349 | [i (exact-truncate (/ (+ y v-offset) item-height))]) 350 | (when (< i (count-items)) 351 | (set! hover-index i))) 352 | (set! hover-index #f)) 353 | (send this refresh)) 354 | 355 | ;; draw a single item in the list 356 | (define/private (paint-item dc item state w h) 357 | (if paint-item-callback 358 | (paint-item-callback this item state dc w h) 359 | (send dc draw-text (~a item) 1 1))) 360 | 361 | ;; default render of all items 362 | (define/private (paint dc) 363 | (let-values ([(w h) (send dc get-size)]) 364 | (when item-color 365 | (send dc set-background item-color)) 366 | (send dc clear) 367 | 368 | ; calculate the visible items and y offset 369 | (let-values ([(q r) (quotient/remainder v-offset item-height)]) 370 | (for ([i (range q (count-items))] 371 | [y (range (- r) h item-height)]) 372 | (let ([item (get-item i)]) 373 | (send dc set-origin 0 y) 374 | (send dc set-clipping-rect 0 0 w item-height) 375 | 376 | ; determine the state of this item 377 | (let* ([state (cond 378 | ((eq? i selected-index) 'selected) 379 | ((eq? i hover-index) 'hover) 380 | ((odd? i) 'alt) 381 | (#t #f))] 382 | 383 | ; determine the background color 384 | [color (match state 385 | ('selected selection-color) 386 | ('hover hover-color) 387 | ('alt alt-color) 388 | (_ #f))]) 389 | 390 | ; draw the background 391 | (when color 392 | (send dc set-pen color 0 'solid) 393 | (send dc set-brush color 'solid) 394 | (send dc draw-rectangle 0 0 w item-height)) 395 | 396 | ; draw the item 397 | (paint-item dc item state w item-height))))) 398 | 399 | ; clear clipping 400 | (send dc set-clipping-region #f))) 401 | 402 | ;; overwrite the items list with a new list of items 403 | (send this set-items items))) 404 | 405 | 406 | 407 | ;; test list 408 | (define (test-canvas-list) 409 | (let* ([frame (new frame% 410 | [label "List Canvas"] 411 | [width 260] 412 | [height 400])] 413 | [canvas (new canvas-list% 414 | [parent frame] 415 | [items (range 1000)] 416 | [context-action-callback (λ (canvas item event) (println item))] 417 | [action-callback (λ (canvas item event) 418 | (send canvas sort-items >))])]) 419 | (send frame show #t))) 420 | -------------------------------------------------------------------------------- /scribblings/canvas-list.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[canvas-list racket/gui]] 4 | 5 | @title{Canvas List} 6 | @author[@author+email["Jeffrey Massung" "massung@gmail.com"]] 7 | 8 | @defmodule[canvas-list] 9 | 10 | A canvas-list is a fast-rendering, single-selection, list control, which allows for complete custom drawing of each item. 11 | 12 | 13 | @;; ---------------------------------------------------- 14 | @section{Example} 15 | 16 | 17 | @racketblock[ 18 | (require racket/gui canvas-list) 19 | 20 | (define frame 21 | (new frame% 22 | [label "List Canvas"] 23 | [width 260] 24 | [height 400])) 25 | 26 | (define canvas 27 | (new canvas-list% 28 | [parent frame] 29 | [items (range 1000)] 30 | [action-callback (λ (canvas item event) 31 | (displayln item))])) 32 | 33 | (send frame show #t) 34 | ] 35 | 36 | 37 | @;; ---------------------------------------------------- 38 | @section{Class} 39 | 40 | @defclass[canvas-list% canvas% ()]{ 41 | A @racket[canvas-list%] is similar in nature to a @racket[list-box%], but instead derives from @racket[canvas%]. This allows for extremely fast, custom rendering of very large lists. It supports single selection, keyboard and mouse navigation, context menus, mouse hovering, alternate row colors, primary key indexing, sorting, filtering, and more. 42 | 43 | @defconstructor[([items sequence? #()] 44 | [item-height exact-nonnegative-integer? 20] 45 | [item-color (or/c (is-a?/c color%) #f) #f] 46 | [alt-color (or/c (is-a?/c color%) #f) #f] 47 | [selection-color (or/c (is-a?/c color%) #f) #f] 48 | [hover-color (or/c (is-a?/c color%) #f) #f] 49 | [force-selection boolean? #f] 50 | [paint-item-callback (or/c ((is-a?/c canvas-list%) 51 | any/c 52 | (or/c 'selected 'hover 'alt #f) 53 | (is-a?/c dc<%>) 54 | exact-nonnegative-integer? 55 | exact-nonnegative-integer? 56 | -> 57 | any/c) 58 | #f) 59 | #f] 60 | [selection-callback (or/c ((is-a?/c canvas-list%) 61 | any/c 62 | (or/c (is-a?/c mouse-event%) #f) 63 | -> 64 | any/c) 65 | #f) 66 | #f] 67 | [action-callback (or/c ((is-a?/c canvas-list%) 68 | any/c 69 | (or/c (is-a?/c mouse-event%) #f) 70 | -> 71 | any/c) 72 | #f) 73 | #f] 74 | [context-action-callback (or/c ((is-a?/c canvas-list%) 75 | any/c 76 | (or/c (is-a?/c mouse-event%) #f) 77 | -> 78 | any/c) 79 | #f) 80 | #f])]{ 81 | Creates a new @racket[canvas-list%] with an initial set of items. 82 | 83 | The @racket[items] are the initial sequence of items that should be displayed. 84 | 85 | The @racket[item-height] is the height - in pixels - afforded for each item to be drawn. This must be the same for every item and (currently) items can not have different heights. 86 | 87 | There are 4 different, basic, background colors that are used to draw items by default. @racket[item-color] is the basic background color used to draw each item and defaults to white. The @racket[alt-color] - if provided - is the background color used for every-other item and defaults to a very light gray. The @racket[selection-color] and @racket[hover-color] are both used to highlight the currently selected item and the item the mouse is currently hovering over. 88 | 89 | The @racket[force-selection] parameter - when @racket[#t] - makes it so that once an item in the list has been selected it cannot be unselected (typically by pressing ESC). 90 | 91 | The @racket[paint-item-callback] is called to render each item in the list. Before being called, the @racket[dc<%>] clipping rect is set and translated to the correct location on screen; drawing at <0,0> will be the upper-left corner of the item's cell. The arguments passed are the @racket[canvas-list%] control, then item to render, the state of the item (whether it's selected, being hovered, or an alternate index), the @racket[dc<%>], and the width and height of the cell. 92 | 93 | The @racket[selection-callback] is called every time the currently selected item changes to a different item in the list. It is passed the @racket[canvas-list%], the item selected, and - if selected with the mouse - the @racket[mouse-event%]. If selection happens using the keyboard (e.g. arrow keys), then @racket[#f] is passed for the event. 94 | 95 | The @racket[action-callback] is called whenever the user double-clicks an item or presses RETURN/ENTER while an item is selected. It is passed the same arguments as the @racket[selection-callback]. 96 | 97 | The @racket[context-action-callback] is called when the user right-clicks an item. Typically this is used to bring up a @racket[popup-menu%]. It is passed the same arguments as the @racket[selection-callback]. 98 | } 99 | 100 | @defmethod[(count-items) exact-nonnegative-integer?]{ 101 | Returns the number of items in the primary key. This may be less than the number of items in the list if there is a filter applied. 102 | } 103 | 104 | @defmethod[(sort-items [less-than? (any/c any/c -> boolean?)] 105 | [#:key key (or/c (any/c -> any/c) #f) #f]) void?]{ 106 | Sorts the items in the list. 107 | } 108 | 109 | @defmethod[(filter-items [pred (any/c -> boolean?)] 110 | [#:key key (or/c (any/c -> any/c) #f) #f]) void?]{ 111 | Applies a filter to the items in the list, hiding any that do not match the predicate. 112 | } 113 | 114 | @defmethod[(reset-primary-key) void?]{ 115 | Removes any applied sorting or filters to the list. 116 | } 117 | 118 | @defmethod[(get-item [index exact-nonnegative-integer?]) any/c]{ 119 | Returns the item at the given index or @racket[#f] if the index is out of range. 120 | } 121 | 122 | @defmethod[(set-items [items sequence?]) void?]{ 123 | Clears the current set of items being displayed and replaces it with a new set. 124 | } 125 | 126 | @defmethod[(insert-items [items sequence?] [index (or/c exact-nonnegative-integer? #f) #f]) void?]{ 127 | Inserts items into the list at the given index. If no index is provided it is inserted before the currently selected index. If there is no selection it is inserted at the beginning. 128 | } 129 | 130 | @defmethod[(append-items [items sequence?] [index (or/c exact-nonnegative-integer? #f) #f]) void?]{ 131 | Appends items onto the list at the given index. If no index is provided it is added after the currently selected index. If there is no selection it is inserted at the beginning. 132 | } 133 | 134 | @defmethod[(get-selected-index) (or/c exact-nonnegative-integer? #f)]{ 135 | Returns the index of the currently selected item or @racket[#f] if there is no selection. 136 | } 137 | 138 | @defmethod[(get-selected-item) any/c]{ 139 | Returns the currently selected item or @racket[#f] if there is no selection. 140 | } 141 | 142 | @defmethod[(get-hover-index) (or/c exact-nonnegative-integer? #f)]{ 143 | Returns the index of the currently hovered item or @racket[#f] if there is no item being hovered over. 144 | } 145 | 146 | @defmethod[(get-hover-item) any/c]{ 147 | Returns the currently hovered item or @racket[#f] if there is no item being hovered over. 148 | } 149 | 150 | @defmethod[(scroll-to-selection) void?]{ 151 | Ensures that the currently selected item is visible. 152 | } 153 | 154 | @defmethod[(open-selected-item) void?]{ 155 | If an @racket[action-callback] was set and there is a selected item, apply the callback. 156 | } 157 | 158 | @defmethod[(select-index [index (or/c exact-nonnegative-integer? #f) hover-index]) void?]{ 159 | Changes the current selection to the index. If not provided, the index of the currently hovered over item is used. If @racket[#f], then the current selection is cleared. 160 | } 161 | 162 | @defmethod[(clear-selection) void?]{ 163 | Clears the current selection. 164 | } 165 | 166 | @defmethod[(select-first) void?]{ 167 | Selects the first item in the list. 168 | } 169 | 170 | @defmethod[(select-last) void?]{ 171 | Selects the last item in the list. 172 | } 173 | 174 | @defmethod[(select-next [#:advance n exact-nonnegative-integer? 1]) void?]{ 175 | Moves to the next item in the list after the current selection. The @racket[advance] parameter is useful when wanting to skip items (e.g. when using page-up/page-down). 176 | } 177 | 178 | @defmethod[(select-previous [#:advance n exact-nonnegative-integer? 1]) void?]{ 179 | Moves to the previous item in the list before the current selection. The @racket[advance] parameter is useful when wanting to skip items (e.g. when using page-up/page-down). 180 | } 181 | 182 | @defmethod[(call-with-selected-item [proc (any/c -> any/c)]) void?]{ 183 | If there is a selected item, call @racket[proc] with the selected item. 184 | } 185 | } 186 | --------------------------------------------------------------------------------