<commit_before>local Layout = require("Layout")
local Rectangle = require("Rectangle")

local Scrollpane = {}

local scrollbar_margin = 7

local quads = {
	up_button =      love.graphics.newQuad(80, 0, 7, 7, 128, 128),
	down_button =    love.graphics.newQuad(80, 9, 7, 7, 128, 128),
	left_button =    love.graphics.newQuad(96, 9, 7, 7, 128, 128),
	right_button =   love.graphics.newQuad(96, 0, 7, 7, 128, 128),
	top_bar =        love.graphics.newQuad(89, 0, 7, 3, 128, 128),
	horizontal_bar = love.graphics.newQuad(89, 3, 7, 1, 128, 128),
	bottom_bar =     love.graphics.newQuad(89, 6, 7, 3, 128, 128),
	left_bar =       love.graphics.newQuad(106, 0, 2, 7, 128, 128),
	vertical_bar =   love.graphics.newQuad(108, 0, 1, 7, 128, 128),
	right_bar =      love.graphics.newQuad(109, 0, 2, 7, 128, 128),
	background =     love.graphics.newQuad(89, 9, 1, 1, 128, 128),
	corner =         love.graphics.newQuad(105, 9, 7, 7, 128, 128),
}

-- Move the scrollpane to focus on the given bounds.
-- Might be restricted by the inner bounds in the scrollpane_state
-- (i.e. when moving to the first item, it will not center the viewport on
-- that item, but rather have the viewport's upper bound line up with the upper
-- bound of that item.)
local apply_focus = function(gui_state, scrollpane_state)
	assert(gui_state)
	assert(scrollpane_state)
	assert(scrollpane_state.focus)

	local bounds = scrollpane_state.focus

	-- We cannot focus on a specific element if we don't know the viewport's
	-- dimensions
	if not (scrollpane_state.w or scrollpane_state.h) then return end

	-- Try to center the scrollpane viewport on the given bounds, but without
	-- exceeding the limits set in the scrollpane state

	local center_bounds = {}
	center_bounds.x, center_bounds.y = Rectangle.center(bounds)
	-- This works because the scrollpane has x, y, w and h
	local center_vp = {}
	center_vp.x, center_vp.y = Rectangle.center(scrollpane_state)

	local dx, dy = center_bounds.x - center_vp.x, center_bounds.y - center_vp.y
	scrollpane_state.x = scrollpane_state.x + dx
	scrollpane_state.y = scrollpane_state.y + dy
	scrollpane_state.tx = scrollpane_state.x
	scrollpane_state.ty = scrollpane_state.y

	scrollpane_state.focus = nil
end

Scrollpane.set_focus = function(scrollpane_state, bounds)
	scrollpane_state.focus = {
		x = bounds.x,
		y = bounds.y,
		w = bounds.w,
		h = bounds.h,
	}
end

Scrollpane.init_scrollpane_state = function(x, y, min_x, min_y, max_x, max_y)
	return  {
		x = x or 0, -- the x offset of the viewport
		y = y or 0, -- the y offset of the viewport
		-- If any of the following are set to nil, then the viewport's movement
		-- is not restricted.
		min_x = min_x, -- the x coordinate below which the viewport cannot be moved
		min_y = min_y, -- the y coordinate below which the viewport cannot be moved
		max_x = max_x, -- the x coordinate above which the viewport cannot be moved
		max_y = max_y, -- the y coordinate above which the viewport cannot be moved
		-- the dimensions of the scrollpane's viewport. will be updated
		-- automatically
		w = nil,
		h = nil,

		-- whether the scrollpane needed scrollbars in the last frame
		had_vertical = false,
		had_horizontal = false,

		-- Scrolling behavior
		tx = 0, -- target translate translation in x
    	ty = 0, -- target translate translation in y
    	last_dx = 0, -- last translate speed in x
	    last_dy = 0, -- last translate speed in y

	    -- Focus, will be applied on the next frame
	    focus = nil,
	}
end

Scrollpane.start = function(state, x, y, w, h, scrollpane_state)
	scrollpane_state = scrollpane_state or Scrollpane.init_scrollpane_state()

	assert(state)
	assert(scrollpane_state)
	x = x or state.layout.next_x
	y = y or state.layout.next_y
	w = w or state.layout.max_w
	h = h or state.layout.max_h

	-- Start a layout that contains this scrollpane
	Layout.start(state, x, y, w, h)
	love.graphics.clear(76, 100, 117)

	-- Calculate the dimension of the viewport, based on whether the viewport
	-- needed a scrollbar in the last frame
	local inner_w = w
	if scrollpane_state.had_vertical then inner_w = inner_w - scrollbar_margin end
	local inner_h = h
	if scrollpane_state.had_horizontal then inner_h = inner_h - scrollbar_margin end

	-- Start a layout that encloses the viewport's content
	Layout.start(state, 0, 0, inner_w, inner_h)

	-- Apply focus if there is one
	if scrollpane_state.focus then
		apply_focus(state, scrollpane_state)
	end

	-- Note the flipped signs
	love.graphics.translate(-scrollpane_state.x, -scrollpane_state.y)

	-- Update the scrollpane's viewport width and height
	scrollpane_state.w = state.layout.max_w
	scrollpane_state.h = state.layout.max_h

	return scrollpane_state
end

Scrollpane.finish = function(state, scrollpane_state, w, h)
	-- If the content defined advance values in x and y, we can detect whether
	-- we need to draw scroll bars at all.
	local content_w = w or state.layout.adv_x
	local content_h = h or state.layout.adv_y

	-- Finish the layout that encloses the viewport's content
	Layout.finish(state)

	local x = state.layout.next_x
	local y = state.layout.next_y
	local w = state.layout.max_w<commit_msg>Update scrollpane's viewport dimensions before changing focus<commit_after>local Layout = require("Layout")
local Rectangle = require("Rectangle")

local Scrollpane = {}

local scrollbar_margin = 7

local quads = {
	up_button =      love.graphics.newQuad(80, 0, 7, 7, 128, 128),
	down_button =    love.graphics.newQuad(80, 9, 7, 7, 128, 128),
	left_button =    love.graphics.newQuad(96, 9, 7, 7, 128, 128),
	right_button =   love.graphics.newQuad(96, 0, 7, 7, 128, 128),
	top_bar =        love.graphics.newQuad(89, 0, 7, 3, 128, 128),
	horizontal_bar = love.graphics.newQuad(89, 3, 7, 1, 128, 128),
	bottom_bar =     love.graphics.newQuad(89, 6, 7, 3, 128, 128),
	left_bar =       love.graphics.newQuad(106, 0, 2, 7, 128, 128),
	vertical_bar =   love.graphics.newQuad(108, 0, 1, 7, 128, 128),
	right_bar =      love.graphics.newQuad(109, 0, 2, 7, 128, 128),
	background =     love.graphics.newQuad(89, 9, 1, 1, 128, 128),
	corner =         love.graphics.newQuad(105, 9, 7, 7, 128, 128),
}

-- Move the scrollpane to focus on the given bounds.
-- Might be restricted by the inner bounds in the scrollpane_state
-- (i.e. when moving to the first item, it will not center the viewport on
-- that item, but rather have the viewport's upper bound line up with the upper
-- bound of that item.)
local apply_focus = function(gui_state, scrollpane_state)
	assert(gui_state)
	assert(scrollpane_state)
	assert(scrollpane_state.focus)

	local bounds = scrollpane_state.focus

	-- We cannot focus on a specific element if we don't know the viewport's
	-- dimensions
	if not (scrollpane_state.w or scrollpane_state.h) then return end

	-- Try to center the scrollpane viewport on the given bounds, but without
	-- exceeding the limits set in the scrollpane state

	local center_bounds = {}
	center_bounds.x, center_bounds.y = Rectangle.center(bounds)
	-- This works because the scrollpane has x, y, w and h
	local center_vp = {}
	center_vp.x, center_vp.y = Rectangle.center(scrollpane_state)

	local dx, dy = center_bounds.x - center_vp.x, center_bounds.y - center_vp.y
	scrollpane_state.x = scrollpane_state.x + dx
	scrollpane_state.y = scrollpane_state.y + dy
	scrollpane_state.tx = scrollpane_state.x
	scrollpane_state.ty = scrollpane_state.y

	scrollpane_state.focus = nil
end

Scrollpane.set_focus = function(scrollpane_state, bounds)
	scrollpane_state.focus = {
		x = bounds.x,
		y = bounds.y,
		w = bounds.w,
		h = bounds.h,
	}
end

Scrollpane.init_scrollpane_state = function(x, y, min_x, min_y, max_x, max_y)
	return  {
		x = x or 0, -- the x offset of the viewport
		y = y or 0, -- the y offset of the viewport
		-- If any of the following are set to nil, then the viewport's movement
		-- is not restricted.
		min_x = min_x, -- the x coordinate below which the viewport cannot be moved
		min_y = min_y, -- the y coordinate below which the viewport cannot be moved
		max_x = max_x, -- the x coordinate above which the viewport cannot be moved
		max_y = max_y, -- the y coordinate above which the viewport cannot be moved
		-- the dimensions of the scrollpane's viewport. will be updated
		-- automatically
		w = nil,
		h = nil,

		-- whether the scrollpane needed scrollbars in the last frame
		had_vertical = false,
		had_horizontal = false,

		-- Scrolling behavior
		tx = 0, -- target translate translation in x
    	ty = 0, -- target translate translation in y
    	last_dx = 0, -- last translate speed in x
	    last_dy = 0, -- last translate speed in y

	    -- Focus, will be applied on the next frame
	    focus = nil,
	}
end

Scrollpane.start = function(state, x, y, w, h, scrollpane_state)
	scrollpane_state = scrollpane_state or Scrollpane.init_scrollpane_state()

	assert(state)
	assert(scrollpane_state)
	x = x or state.layout.next_x
	y = y or state.layout.next_y
	w = w or state.layout.max_w
	h = h or state.layout.max_h

	-- Start a layout that contains this scrollpane
	Layout.start(state, x, y, w, h)
	love.graphics.clear(76, 100, 117)

	-- Calculate the dimension of the viewport, based on whether the viewport
	-- needed a scrollbar in the last frame
	local inner_w = w
	if scrollpane_state.had_vertical then inner_w = inner_w - scrollbar_margin end
	local inner_h = h
	if scrollpane_state.had_horizontal then inner_h = inner_h - scrollbar_margin end

	-- Start a layout that encloses the viewport's content
	Layout.start(state, 0, 0, inner_w, inner_h)

	-- Update the scrollpane's viewport width and height
	scrollpane_state.w = state.layout.max_w
	scrollpane_state.h = state.layout.max_h

	-- Apply focus if there is one
	if scrollpane_state.focus then
		apply_focus(state, scrollpane_state)
	end

	-- Note the flipped signs
	love.graphics.translate(-scrollpane_state.x, -scrollpane_state.y)

	return scrollpane_state
end

Scrollpane.finish = function(state, scrollpane_state, w, h)
	-- If the content defined advance values in x and y, we can detect whether
	-- we need to draw scroll bars at all.
	local content_w = w or state.layout.adv_x
	local content_h = h or state.layout.adv_y

	-- Finish the layout that encloses the viewport's content
	Layout.finish(state)

	local x = state.layout.next_x
	local y = state.layout.next_y
	local w = state.layout.max_w
{'fraction_non_alphanumeric': 0.09305259072385018, 'fraction_numerical': 0.02969144187851737, 'mean_word_length': 3.611633109619687, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '780955', 'n_tokens_mistral': 3906, 'n_tokens_neox': 3504, 'n_words': 1207}