So let me just get this right out of the way, I'm using SDL1.2 (Lispbuilder-sdl specifically, as I'm using Common Lisp), so on to the problem. I'm trying to make a scrollbar that can scroll text in an text-area, I already searched for an answer on this (google, here, etc) and I found this:

And since a picture says more than a thousand words, here's a screenshot of the issue (The scrollbox is 30 pixels in height): here

So what I essentially want, is for the last line to be drawn when the box has gone all the way down, rather than continue scrolling after.

So a few notes before the code:

text-scrolling is only doing vertical scrolling as I want to solve this before making it work horizontally as well, the scrolling portion of the code (that handles moving of the scroll-box) supports both.

Hitboxes are used for the collision checking, something I'll rewrite at a later time, but essentially it's a rectangle with an absolute position that is used by collision-checking for the mouse (in this case, outside of this it holds more parameters than just position).

I use aux (local variable) for objects (such as the scroll box within scroll-bar), let (also local variable) is used in all other cases (especially if logic is involved).

;;Text-area scrolling: 

(defun get-max-box-pos (scroll-bar)
  (- (h scroll-bar) (h (get-scroll-box scroll-bar))))

(defun text-field-shown-lines (text-field)
  (floor (/ (h text-field) (sdl:get-font-size " " :size :h :font (get-text-font text-field)))))

(defun line-pixels (text-field)
  (* (- (get-line-amount text-field) (text-field-shown-lines text-field)) (sdl:get-font-size " " :size :h :font (get-text-font text-field))))

(defun get-movement-rate (text-field scroll-bar)
  (let* ((max-box-pos (get-max-box-pos scroll-bar)))
    (- (ceiling (/ (line-pixels text-field) (if (= max-box-pos 0) 1 max-box-pos))))))

(defun text-scrolling (text-field scroll-bar texts &key (color (shf:get-color black))
                             &aux (scroll-box (get-scroll-box scroll-bar)))
  (scrolling scroll-bar)
  (setf (get-text-y text-field) (* (get-movement-rate text-field scroll-bar) (y scroll-box))))

So the thing I can't figure out\need help with, is how to calculate how much to move with, to ensure all the text can be scrolled (irregardless of the size of the scroll box), and not be too far apart.

Answer in the link provided (and my solution done before I checked that) causes it to move beyond.

Most of relevant\needed code (outside of drawing, instances, loops, etc):

;; Classes: 
(defclass pos ()
  ((x :initarg :x :accessor x)
   (y :initarg :y :accessor y))
  (:documentation "Positions of an object, used as a super class for circle and rect"))

(defclass rect (pos)
  ((w :initarg :w :accessor w)
   (h :initarg :h :accessor h))
  (:documentation "A rectangle"))

(defclass text-field (rect)
  ((surface :accessor get-surface
        :initarg :surface)
   (text :accessor get-text
          :initarg :text
          :documentation "text to be used on the text-field")
   (text-x :accessor get-text-x
       :initarg :text-x)
   (text-y :accessor get-text-y
       :initarg :text-y)
   (state :accessor get-text-field-state
      :initarg :state)
   (color :accessor get-box-color
      :initarg :color)
   (alpha :accessor get-alpha
      :initarg :alpha)
   (font :accessor get-text-font
     :initarg :font
     :Documentation "Font used by the textfield")
   (background :accessor text-field-background
           :initarg :background)
   (amount-of-lines :accessor get-line-amount
            :Documentation "The amount of lines the text-field hold"
            :initarg :line-amount)))

(defclass scroll-box (rect)
  ((surface :accessor get-surface
        :initarg :surface)
   (color :accessor get-box-color
      :initarg :color)
   (direction :accessor get-box-dir
          :initarg :dir
          :documentation "what cordinate to scroll")
   (hitbox :accessor get-hitbox
       :initarg :hitbox)
   (active :accessor is-active-box?
       :initform nil
       :documentation "if the mouse is currently engaged in this box")))

(defclass scroll-bar (rect)
  ((surface :accessor get-surface
        :initarg :surface)
   (show :accessor show-scroll-bar?
     :initarg :show)
   (scroll-box :accessor get-scroll-box :initarg :scroll-box)
   (hitbox :accessor get-hitbox :initarg :hitbox)
   (bar-color :accessor get-bar-color :initarg :bar-color)
   (box-color :accessor get-box-color :initarg :box-color)))

;; The scrolling code:

(defun scroll-box-active-mouse? (scroll-box )
  "Changes the active state of the scroll-box if mouse is clicked on it"
  (and (shf:mouse-collision-check (get-hitbox scroll-box)) (sdl:mouse-left-p)
       (setf (is-active-box? scroll-box) t)))

(defun out-of-bounds? (box-pos box-size bar-size)
  "Checks if the scroll box is out of bound of the bar"
  (cond ((<= box-pos 0)
    ((>= (+ box-size box-pos) bar-size)

(defun get-new-scroll-box-pos (mouse-pos bar-pos bar-size box-pos  box-size)
  "Returns the new position of the scroll box and it's hitbox in relative to the mouse position"
  (let ((bounds (out-of-bounds? box-pos box-size bar-size)))
    (cond ((and (string-equal bounds 'start) (< mouse-pos (+ bar-pos (round (/ box-size 2)))))
       (values 0 bar-pos))
      ((and (string-equal bounds 'end) (> mouse-pos (- (+ bar-size bar-pos) (round (/ box-size 2)))))
       (values (- bar-size box-size)
           (+ bar-pos box-pos)))
      (t (values (- (- mouse-pos bar-pos) (round (/ box-size 2)))
             (- mouse-pos (round (/ box-size 2))))))))

(defun scrolling (scroll-bar &aux (scroll-box (get-scroll-box scroll-bar))
                   (hitbox (get-hitbox scroll-box)))
  "Call to automatically check for, and cause scrolling. 
Destructivly change the positions of scroll-bar, scroll-box(and it's hitbox)"
  (scroll-box-active-mouse? scroll-box)
  (when (is-active-box? scroll-box)
    (cond ((string-equal (get-box-dir scroll-box) :y)
       (setf (values (y scroll-box) (y hitbox))
         (get-new-scroll-box-pos (sdl:mouse-y) (y scroll-bar)  (h scroll-bar)
                     (y scroll-box) (h scroll-box))))

      ((string-equal (get-box-dir scroll-box) :x)
       (setf (values (x scroll-box) (x hitbox))
         (get-new-scroll-box-pos (sdl:mouse-x) (y scroll-bar) (w scroll-bar)
                     (x scroll-box) (w scroll-box)))))))

Edit: To clarify the 30 pixel box, it's 30 pixels (abritrary size, point is to have a pre-defined size of it) as I want to be able to do scrolling with a box that's already of a specific size (such as say, a circular sprite used for the box\scrolling, rather than a gray box).

Edit 2: I'm still not able to figure it out, anyway here's a pastebin snippet of the code to be able to run by itself(so any and all unrelated code removed, etc, only left in what is needed to get things up and running\working). Obviously need a Common Lisp enviornment with either quicklisp or ASDF to load up lispbuilder-sdl (ttf), and cl-utilities (read top comment of the snippet), personally using vera font, but obviously shouldn't matter since sdl gets the font height eitherway.

line 434 is where the scrolling of the box is done\start (anything before this point is drawing, classes, etc)

Line 472 is where the text-field scrolling is done, and where I am having problems

Line 478 is where the text is being moved

Line 504 is the main function (where you have to change the font path)

  • \$\begingroup\$ I can only help with the scrolling stop at the moment. If you know the height of each text line (in pixels), you can add all heights together to get the complete size that should scroll and where the scrolling should stop. \$\endgroup\$ Nov 3, 2019 at 10:55
  • \$\begingroup\$ Yeah I have access to the height of each line, and that's kind of what line-pixel does(horrible name, lol), it gets the total amount of pixels not shown in scrolling, essentially. Changing it to be the total amount of pixels of all the text doesn't amount to much difference, as the math will find the same amount to scroll by: box-height / bar-height * text-total-size = 1.6, but no such thing as 1.6 pixels, so rounded up to 2 pixels, leaving the space. \$\endgroup\$
    – Lalzy
    Nov 3, 2019 at 11:58

1 Answer 1


For anyone else stumbling on this in the future, the only solution I found is to stop the text from scrolling(leaving a empty-space for the scroll-box where moving it will have no effect anymore), which I think is what Daniel_1985 alluded too in his comment.

The only way to circumvent the scroll-box empty-space issue(That I have found), is by having it resize based on amount of elements to scroll(however you decide to do and calculate scrolling).

So if someone do know, or manage to figure out how to do this without resizing the box, and not leaving any empty space for either elements in the field, or the scroll-box, I'd love to hear it!


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .