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):
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)
'start)
((>= (+ box-size box-pos) bar-size)
'end)))
(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)